From 6917b940d6a0c4146459817da7e3f5675e65d1d2 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Sat, 30 Nov 2024 20:56:18 +0100 Subject: [PATCH 1/3] Add list primitives from Quartic speedups paper --- .../qualtran_dev_tools/notebook_specs.py | 10 + qualtran/bloqs/arithmetic/lists/__init__.py | 16 ++ .../bloqs/arithmetic/lists/has_duplicates.py | 177 ++++++++++++++++++ .../arithmetic/lists/has_duplicates_test.py | 57 ++++++ .../bloqs/arithmetic/lists/sort_in_place.py | 99 ++++++++++ .../arithmetic/lists/symmetric_difference.py | 130 +++++++++++++ .../lists/symmetric_difference_test.py | 63 +++++++ qualtran/bloqs/arithmetic/sorting.py | 11 +- 8 files changed, 558 insertions(+), 5 deletions(-) create mode 100644 qualtran/bloqs/arithmetic/lists/__init__.py create mode 100644 qualtran/bloqs/arithmetic/lists/has_duplicates.py create mode 100644 qualtran/bloqs/arithmetic/lists/has_duplicates_test.py create mode 100644 qualtran/bloqs/arithmetic/lists/sort_in_place.py create mode 100644 qualtran/bloqs/arithmetic/lists/symmetric_difference.py create mode 100644 qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index 292f56869..79e61306d 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -37,6 +37,7 @@ import qualtran.bloqs.arithmetic.controlled_add_or_subtract import qualtran.bloqs.arithmetic.controlled_addition import qualtran.bloqs.arithmetic.conversions +import qualtran.bloqs.arithmetic.lists import qualtran.bloqs.arithmetic.multiplication import qualtran.bloqs.arithmetic.negate import qualtran.bloqs.arithmetic.permutation @@ -492,6 +493,15 @@ module=qualtran.bloqs.arithmetic.trigonometric, bloq_specs=[qualtran.bloqs.arithmetic.trigonometric.arcsin._ARCSIN_DOC], ), + NotebookSpecV2( + title='List Functions', + module=qualtran.bloqs.arithmetic.lists, + bloq_specs=[ + qualtran.bloqs.arithmetic.lists.sort_in_place._SORT_IN_PLACE_DOC, + qualtran.bloqs.arithmetic.lists.symmetric_difference._SYMMETRIC_DIFFERENCE_DOC, + qualtran.bloqs.arithmetic.lists.has_duplicates._HAS_DUPLICATES_DOC, + ], + ), ] MOD_ARITHMETIC = [ diff --git a/qualtran/bloqs/arithmetic/lists/__init__.py b/qualtran/bloqs/arithmetic/lists/__init__.py new file mode 100644 index 000000000..7ed84457f --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/__init__.py @@ -0,0 +1,16 @@ +# 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 .has_duplicates import HasDuplicates +from .sort_in_place import SortInPlace +from .symmetric_difference import SymmetricDifference diff --git a/qualtran/bloqs/arithmetic/lists/has_duplicates.py b/qualtran/bloqs/arithmetic/lists/has_duplicates.py new file mode 100644 index 000000000..cce9c88d2 --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/has_duplicates.py @@ -0,0 +1,177 @@ +# 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 collections import Counter +from typing import Union + +import attrs +import numpy as np +from attrs import frozen + +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CtrlSpec, + QBit, + QInt, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic import LinearDepthHalfLessThan +from qualtran.bloqs.basic_gates import CNOT, XGate +from qualtran.bloqs.mcmt import MultiControlX +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.simulation.classical_sim import ClassicalValT +from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt + + +@frozen +class HasDuplicates(Bloq): + r"""Given a sorted list of `l` numbers, check if it contains any duplicates. + + Produces a single qubit which is `1` if there are duplicates, and `0` if all are disjoint. + It compares every adjacent pair, and therefore uses `l - 1` comparisons. + It then uses a single MCX on `l - 1` bits gate to compute the flag. + + Args: + l: number of elements in the list + dtype: type of each element to store `[n]`. + + Registers: + xs: a list of `l` registers of `dtype`. + flag: single qubit. Value is flipped if the input list has duplicates, otherwise stays same. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Lemma 4.12. Eq. 122. + """ + + l: SymbolicInt + dtype: Union[QUInt, QInt] + is_controlled: bool = False + + @property + def signature(self) -> 'Signature': + registers = [Register('xs', self.dtype, shape=(self.l,)), Register('flag', QBit())] + if self.is_controlled: + registers.append(Register('ctrl', QBit())) + return Signature(registers) + + @property + def _le_bloq(self) -> LinearDepthHalfLessThan: + return LinearDepthHalfLessThan(self.dtype) + + def build_composite_bloq( + self, bb: 'BloqBuilder', xs: 'SoquetT', flag: 'Soquet', **extra_soqs: 'SoquetT' + ) -> dict[str, 'SoquetT']: + assert not is_symbolic(self.l) + assert isinstance(xs, np.ndarray) + + cs = [] + oks = [] + if self.is_controlled: + oks = [extra_soqs.pop('ctrl')] + assert not extra_soqs + + for i in range(1, self.l): + xs[i - 1], xs[i], c, ok = bb.add(self._le_bloq, a=xs[i - 1], b=xs[i]) + cs.append(c) + oks.append(ok) + + oks, flag = bb.add(MultiControlX((1,) * len(oks)), controls=np.array(oks), target=flag) + if not self.is_controlled: + flag = bb.add(XGate(), q=flag) + else: + oks[0], flag = bb.add(CNOT(), ctrl=oks[0], target=flag) + + oks = list(oks) + for i in reversed(range(1, self.l)): + xs[i - 1], xs[i] = bb.add( + self._le_bloq.adjoint(), a=xs[i - 1], b=xs[i], c=cs.pop(), target=oks.pop() + ) + + if self.is_controlled: + extra_soqs = {'ctrl': oks.pop()} + assert not oks + + return {'xs': xs, 'flag': flag} | extra_soqs + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + counts = Counter[Bloq]() + + counts[self._le_bloq] += self.l - 1 + counts[self._le_bloq.adjoint()] += self.l - 1 + + n_ctrls = self.l - (1 if not self.is_controlled else 0) + counts[MultiControlX(HasLength(n_ctrls))] += 1 + + counts[XGate() if not self.is_controlled else CNOT()] += 1 + + return counts + + def on_classical_vals(self, **vals: 'ClassicalValT') -> dict[str, 'ClassicalValT']: + xs = np.asarray(vals['xs']) + assert np.all(xs == np.sort(xs)) + if np.any(xs[:-1] == xs[1:]): + vals['flag'] ^= 1 + return vals + + def adjoint(self) -> 'HasDuplicates': + return self + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=1 if self.is_controlled else None, + bloq_with_ctrl=attrs.evolve(self, is_controlled=True), + ctrl_reg_name='ctrl', + ) + + +@bloq_example +def _has_duplicates() -> HasDuplicates: + has_duplicates = HasDuplicates(4, QUInt(3)) + return has_duplicates + + +@bloq_example +def _has_duplicates_symb() -> HasDuplicates: + import sympy + + n = sympy.Symbol("n") + has_duplicates_symb = HasDuplicates(4, QUInt(n)) + return has_duplicates_symb + + +@bloq_example +def _has_duplicates_symb_len() -> HasDuplicates: + import sympy + + l, n = sympy.symbols("l n") + has_duplicates_symb_len = HasDuplicates(l, QUInt(n)) + return has_duplicates_symb_len + + +_HAS_DUPLICATES_DOC = BloqDocSpec( + bloq_cls=HasDuplicates, + examples=[_has_duplicates_symb, _has_duplicates, _has_duplicates_symb_len], +) diff --git a/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py b/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py new file mode 100644 index 000000000..62ef23aa3 --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py @@ -0,0 +1,57 @@ +# 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. +import itertools + +import numpy as np +import pytest + +import qualtran.testing as qlt_testing +from qualtran import QInt, QUInt + +from .has_duplicates import ( + _has_duplicates, + _has_duplicates_symb, + _has_duplicates_symb_len, + HasDuplicates, +) + + +@pytest.mark.parametrize( + "bloq_ex", + [_has_duplicates, _has_duplicates_symb, _has_duplicates_symb_len], + ids=lambda b: b.name, +) +def test_examples(bloq_autotester, bloq_ex): + bloq_autotester(bloq_ex) + + +@pytest.mark.parametrize("bloq_ex", [_has_duplicates, _has_duplicates_symb], ids=lambda b: b.name) +def test_counts(bloq_ex): + qlt_testing.assert_equivalent_bloq_counts(bloq_ex()) + + +@pytest.mark.parametrize("l", [2, 3, pytest.param(4, marks=pytest.mark.slow)]) +@pytest.mark.parametrize( + "dtype", [QUInt(2), QInt(2), pytest.param(QUInt(3), marks=pytest.mark.slow)] +) +def test_classical_action(l, dtype): + bloq = HasDuplicates(l, dtype) + cbloq = bloq.decompose_bloq() + + for xs_t in itertools.product(dtype.get_classical_domain(), repeat=l): + xs = np.sort(xs_t) + for flag in [0, 1]: + np.testing.assert_equal( + cbloq.call_classically(xs=xs, flag=flag), bloq.call_classically(xs=xs, flag=flag) + ) diff --git a/qualtran/bloqs/arithmetic/lists/sort_in_place.py b/qualtran/bloqs/arithmetic/lists/sort_in_place.py new file mode 100644 index 000000000..d4da3727a --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/sort_in_place.py @@ -0,0 +1,99 @@ +# 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 collections import Counter + +from attrs import frozen + +from qualtran import Bloq, BloqDocSpec, BQUInt, QDType, Register, Signature +from qualtran.bloqs.arithmetic import Xor +from qualtran.bloqs.arithmetic.sorting import Comparator +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ceil, log2, SymbolicInt + + +@frozen +class SortInPlace(Bloq): + r"""Sort a list of $\ell$ numbers in place using $\ell \log \ell$ ancilla bits. + + Applies the map: + $$ + |x_1, x_2, \ldots, x_l\rangle + |0^{\ell \log \ell}\rangle + \mapsto + |x_{\pi_1}, x_{\pi_2}, \ldots, x_{\pi_\ell})\rangle + |\pi_1, \pi_2, \ldots, \pi_\ell\rangle + $$ + where $x_{\pi_1} \le x_{\pi_2} \ldots \le x_{\pi_\ell}$ is the sorted list, + and the ancilla are entangled. + + To apply this, we first use any sorting algorithm to output the sorted list + in a clean register. And then use the following algorithm from Lemma 4.12 of Ref [1] + that applies the map: + + $$ + |x_1, ..., x_l\rangle|x_{\pi(1)}, ..., x_{\pi(l)})\rangle + \mapsto + |x_l, ..., x_l\rangle|\pi(1), ..., \pi(l))\rangle + $$ + + where $x_i \in [n]$ and $\pi(i) \in [l]$. + This second algorithm (Lemma 4.12) has two steps, each with $l^2$ comparisons: + 1. compute `pi(1) ... pi(l)` given `x_1 ... x_l` and `x_{pi(1)} ... x{pi(l)}`. + 1. (un)compute `x_{pi(1)} ... x{pi(l)}` using `pi(1) ... pi(l)` given `x_1 ... x_l`. + + Args: + l: number of elements in the list + dtype: type of each element to store `[n]`. + + Registers: + input: the entire input as a single register + ancilla (RIGHT): the generated (entangled) register storing `pi`. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Lemma 4.12. Eq. 122. + """ + + l: SymbolicInt + dtype: QDType + + @property + def signature(self) -> 'Signature': + return Signature( + [ + Register('xs', self.dtype, shape=(self.l,)), + Register('pi', self.index_dtype, shape=(self.l,)), + ] + ) + + @property + def index_dtype(self) -> QDType: + """dtype to represent an index in range `[l]`""" + bitsize = ceil(log2(self.l)) + return BQUInt(bitsize, self.l) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + compare = Comparator(self.dtype.num_qubits) + n_ops = 3 * self.l**2 + + counts = Counter[Bloq]() + + counts[compare] += n_ops + counts[compare.adjoint()] += n_ops + counts[Xor(self.dtype)] += n_ops + + return counts + + +_SORT_IN_PLACE_DOC = BloqDocSpec(bloq_cls=SortInPlace, examples=[]) diff --git a/qualtran/bloqs/arithmetic/lists/symmetric_difference.py b/qualtran/bloqs/arithmetic/lists/symmetric_difference.py new file mode 100644 index 000000000..3d18d5689 --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/symmetric_difference.py @@ -0,0 +1,130 @@ +# 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 collections import Counter + +from attrs import frozen + +from qualtran import Bloq, bloq_example, BloqDocSpec, QBit, QDType, QUInt, Register, Signature +from qualtran.bloqs.arithmetic import Equals, EqualsAConstant, HammingWeightCompute, Xor +from qualtran.bloqs.arithmetic.sorting import BitonicMerge +from qualtran.bloqs.basic_gates import CNOT +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import bit_length, is_symbolic, SymbolicInt + + +@frozen +class SymmetricDifference(Bloq): + r"""Given two sorted sets $S, T$ of unique elements, compute their symmetric difference. + + This accepts an integer `n_diff`, and marks a flag qubit if the symmetric difference + set is of size exactly `n_diff`. If the flag is marked (1), then the output of `n_diff` + numbers is the symmetric difference, otherwise it may be arbitrary. + + Args: + n_lhs: number of elements in $S$ + n_rhs: number of elements in $T$ + n_diff: expected number of elements in the difference $S \Delta T$. + dtype: type of each element. + + Registers: + S: list of `n_lhs` numbers. + T: list of `n_rhs` numbers. + diff: output register of `n_diff` numbers. + flag: 1 if there are duplicates, 0 if all are unique. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Theorem 4.17, proof para 3, page 38. + """ + + n_lhs: SymbolicInt + n_rhs: SymbolicInt + n_diff: SymbolicInt + dtype: QDType + + def __attrs_post_init__(self): + if not is_symbolic(self.n_lhs, self.n_rhs): + assert self.n_lhs >= self.n_rhs, "lhs must be the larger set" + + @property + def signature(self) -> 'Signature': + return Signature( + [ + Register('S', self.dtype, shape=(self.n_lhs,)), + Register('T', self.dtype, shape=(self.n_rhs,)), + Register('diff', self.dtype, shape=(self.n_diff,)), + Register('flag', QBit()), + ] + ) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + # the forward pass, i.e. all bloqs that must be uncomputed + counts_forward = Counter[Bloq]() + + # merge the lists + counts_forward[BitonicMerge(self.n_lhs, self.dtype.num_qubits)] += 1 + # compare adjacents + counts_forward[Equals(self.dtype)] += self.n_lhs + self.n_rhs - 1 + # compute number of equal adjacents + counts_forward[HammingWeightCompute(self.n_lhs + self.n_rhs - 1)] += 1 + # check: 2 * n_equal = n_lhs + n_rhs - n_diff + # (note: the above eq holds as we assume all input elements are unique) + counts_forward[ + EqualsAConstant( + bit_length(self.n_lhs + self.n_rhs - 1), + (self.n_lhs + self.n_rhs - self.n_diff) // 2, + ) + ] += 1 + + # all bloqs + counts = Counter[Bloq]() + + # copy the first n_diff numbers and flag + counts[Xor(self.dtype)] += self.n_diff + counts[CNOT()] += 1 + + for bloq, n in counts_forward.items(): + counts[bloq] += n + counts[bloq.adjoint()] += n + + return counts + + +@bloq_example +def _symm_diff() -> SymmetricDifference: + import sympy + + from qualtran.symbolics import bit_length + + n, k, c = sympy.symbols("n k c", positive=True, integer=True) + dtype = QUInt(bit_length(n - 1)) + symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, dtype=dtype) + return symm_diff + + +@bloq_example +def _symm_diff_equal_size() -> SymmetricDifference: + import sympy + + from qualtran.symbolics import bit_length + + n, k, c = sympy.symbols("n k c", positive=True, integer=True) + dtype = QUInt(bit_length(n - 1)) + symm_diff_equal_size = SymmetricDifference(n_lhs=c * k, n_rhs=c * k, n_diff=k, dtype=dtype) + return symm_diff_equal_size + + +_SYMMETRIC_DIFFERENCE_DOC = BloqDocSpec( + bloq_cls=SymmetricDifference, examples=[_symm_diff, _symm_diff_equal_size] +) diff --git a/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py b/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py new file mode 100644 index 000000000..fa3765f70 --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py @@ -0,0 +1,63 @@ +# 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 unittest.mock import ANY + +import pytest + +from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost +from qualtran.symbolics import ceil, log2 + +from .symmetric_difference import _symm_diff, _symm_diff_equal_size + + +@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +def test_cost(bloq_ex): + bloq = bloq_ex() + gc = get_cost_value(bloq, QECGatesCost()) + + l, r = bloq.n_lhs, bloq.n_rhs # assumption l >= r + logn = bloq.dtype.num_qubits + logl = ceil(log2(l)) + assert gc == GateCounts( + cswap=2 * l * logn * (logl + 1), + and_bloq=( + 2 * l * (2 * logn + 1) * (logl + 1) + + l + + r + + 2 * ((logn - 1) * (l + r - 1)) + + 2 * ceil(log2(l + r)) + - 4 + ), + clifford=ANY, + measurement=ANY, + ) + + # \tilde{O}(l log n) + # Page 38, Thm 4.17, proof para 3, 3rd last line. + assert gc.total_t_count() in big_O(l * logn * logl**2) + + +@pytest.mark.notebook +def test_notebook(): + from qualtran.testing import execute_notebook + + execute_notebook('arithmetic') diff --git a/qualtran/bloqs/arithmetic/sorting.py b/qualtran/bloqs/arithmetic/sorting.py index d18db924e..c500effc9 100644 --- a/qualtran/bloqs/arithmetic/sorting.py +++ b/qualtran/bloqs/arithmetic/sorting.py @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeNotImplementedError, DecomposeTypeError, QBit, QUInt, @@ -222,8 +223,6 @@ def __attrs_post_init__(self): k = self.half_length if not is_symbolic(k): assert k >= 1, "length of input lists must be positive" - # TODO(#1090) support non-power-of-two input lengths - assert (k & (k - 1)) == 0, "length of input lists must be a power of 2" @cached_property def signature(self) -> 'Signature': @@ -249,14 +248,16 @@ def is_symbolic(self): def build_composite_bloq( self, bb: 'BloqBuilder', xs: 'SoquetT', ys: 'SoquetT' ) -> dict[str, 'SoquetT']: - if is_symbolic(self.half_length): + k = self.half_length + if is_symbolic(k): raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") + if (k & (k - 1)) == 0: + # TODO(#1090) support non-power-of-two input lengths + raise DecomposeNotImplementedError("length of input lists must be a power of 2") assert isinstance(xs, np.ndarray) assert isinstance(ys, np.ndarray) - k = self.half_length - first_round_junk = [] for i in range(k): xs[i], ys[k - 1 - i], anc = bb.add(Comparator(self.bitsize), a=xs[i], b=ys[k - 1 - i]) From 6e2fdac9521b34d64f3e710f392bcc6419d494d0 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Sun, 1 Dec 2024 19:18:38 +0100 Subject: [PATCH 2/3] notebook --- docs/bloqs/index.rst | 1 + qualtran/bloqs/arithmetic/lists/lists.ipynb | 380 ++++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 qualtran/bloqs/arithmetic/lists/lists.ipynb diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 3ed5dfe35..969a42419 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -75,6 +75,7 @@ Bloqs Library arithmetic/permutation.ipynb arithmetic/bitwise.ipynb arithmetic/trigonometric/trigonometric.ipynb + arithmetic/lists/lists.ipynb .. toctree:: :maxdepth: 2 diff --git a/qualtran/bloqs/arithmetic/lists/lists.ipynb b/qualtran/bloqs/arithmetic/lists/lists.ipynb new file mode 100644 index 000000000..cd9ebf669 --- /dev/null +++ b/qualtran/bloqs/arithmetic/lists/lists.ipynb @@ -0,0 +1,380 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4b6002bc", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# List Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73f38d89", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "75d1819f", + "metadata": { + "cq.autogen": "SortInPlace.bloq_doc.md" + }, + "source": [ + "## `SortInPlace`\n", + "Sort a list of $\\ell$ numbers in place using $\\ell \\log \\ell$ ancilla bits.\n", + "\n", + "Applies the map:\n", + "$$\n", + " |x_1, x_2, \\ldots, x_l\\rangle\n", + " |0^{\\ell \\log \\ell}\\rangle\n", + " \\mapsto\n", + " |x_{\\pi_1}, x_{\\pi_2}, \\ldots, x_{\\pi_\\ell})\\rangle\n", + " |\\pi_1, \\pi_2, \\ldots, \\pi_\\ell\\rangle\n", + "$$\n", + "where $x_{\\pi_1} \\le x_{\\pi_2} \\ldots \\le x_{\\pi_\\ell}$ is the sorted list,\n", + "and the ancilla are entangled.\n", + "\n", + "To apply this, we first use any sorting algorithm to output the sorted list\n", + "in a clean register. And then use the following algorithm from Lemma 4.12 of Ref [1]\n", + "that applies the map:\n", + "\n", + "$$\n", + " |x_1, ..., x_l\\rangle|x_{\\pi(1)}, ..., x_{\\pi(l)})\\rangle\n", + " \\mapsto\n", + " |x_l, ..., x_l\\rangle|\\pi(1), ..., \\pi(l))\\rangle\n", + "$$\n", + "\n", + "where $x_i \\in [n]$ and $\\pi(i) \\in [l]$.\n", + "This second algorithm (Lemma 4.12) has two steps, each with $l^2$ comparisons:\n", + "1. compute `pi(1) ... pi(l)` given `x_1 ... x_l` and `x_{pi(1)} ... x{pi(l)}`.\n", + "1. (un)compute `x_{pi(1)} ... x{pi(l)}` using `pi(1) ... pi(l)` given `x_1 ... x_l`.\n", + "\n", + "#### Parameters\n", + " - `l`: number of elements in the list\n", + " - `dtype`: type of each element to store `[n]`. \n", + "\n", + "#### Registers\n", + " - `input`: the entire input as a single register\n", + " - `ancilla`: the generated (entangled) register storing `pi`. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Lemma 4.12. Eq. 122.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fcb6c012", + "metadata": { + "cq.autogen": "SortInPlace.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic.lists import SortInPlace" + ] + }, + { + "cell_type": "markdown", + "id": "e878a52b", + "metadata": { + "cq.autogen": "SymmetricDifference.bloq_doc.md" + }, + "source": [ + "## `SymmetricDifference`\n", + "Given two sorted sets $S, T$ of unique elements, compute their symmetric difference.\n", + "\n", + "This accepts an integer `n_diff`, and marks a flag qubit if the symmetric difference\n", + "set is of size exactly `n_diff`. If the flag is marked (1), then the output of `n_diff`\n", + "numbers is the symmetric difference, otherwise it may be arbitrary.\n", + "\n", + "#### Parameters\n", + " - `n_lhs`: number of elements in $S$\n", + " - `n_rhs`: number of elements in $T$\n", + " - `n_diff`: expected number of elements in the difference $S \\Delta T$.\n", + " - `dtype`: type of each element. \n", + "\n", + "#### Registers\n", + " - `S`: list of `n_lhs` numbers.\n", + " - `T`: list of `n_rhs` numbers.\n", + " - `diff`: output register of `n_diff` numbers.\n", + " - `flag`: 1 if there are duplicates, 0 if all are unique. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Theorem 4.17, proof para 3, page 38.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccaa8a4b", + "metadata": { + "cq.autogen": "SymmetricDifference.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic.lists import SymmetricDifference" + ] + }, + { + "cell_type": "markdown", + "id": "6cefce9f", + "metadata": { + "cq.autogen": "SymmetricDifference.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7ffc68f", + "metadata": { + "cq.autogen": "SymmetricDifference.symm_diff" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.symbolics import bit_length\n", + "\n", + "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", + "dtype = QUInt(bit_length(n - 1))\n", + "symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, dtype=dtype)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4da0b292", + "metadata": { + "cq.autogen": "SymmetricDifference.symm_diff_equal_size" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.symbolics import bit_length\n", + "\n", + "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", + "dtype = QUInt(bit_length(n - 1))\n", + "symm_diff_equal_size = SymmetricDifference(n_lhs=c * k, n_rhs=c * k, n_diff=k, dtype=dtype)" + ] + }, + { + "cell_type": "markdown", + "id": "79bb967b", + "metadata": { + "cq.autogen": "SymmetricDifference.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb210e38", + "metadata": { + "cq.autogen": "SymmetricDifference.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([symm_diff, symm_diff_equal_size],\n", + " ['`symm_diff`', '`symm_diff_equal_size`'])" + ] + }, + { + "cell_type": "markdown", + "id": "946e2cef", + "metadata": { + "cq.autogen": "SymmetricDifference.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a25a154", + "metadata": { + "cq.autogen": "SymmetricDifference.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "symm_diff_g, symm_diff_sigma = symm_diff.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(symm_diff_g)\n", + "show_counts_sigma(symm_diff_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "a78e7e32", + "metadata": { + "cq.autogen": "HasDuplicates.bloq_doc.md" + }, + "source": [ + "## `HasDuplicates`\n", + "Given a sorted list of `l` numbers, check if it contains any duplicates.\n", + "\n", + "Produces a single qubit which is `1` if there are duplicates, and `0` if all are disjoint.\n", + "It compares every adjacent pair, and therefore uses `l - 1` comparisons.\n", + "It then uses a single MCX on `l - 1` bits gate to compute the flag.\n", + "\n", + "#### Parameters\n", + " - `l`: number of elements in the list\n", + " - `dtype`: type of each element to store `[n]`. \n", + "\n", + "#### Registers\n", + " - `xs`: a list of `l` registers of `dtype`.\n", + " - `flag`: single qubit. Value is flipped if the input list has duplicates, otherwise stays same. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Lemma 4.12. Eq. 122.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0707263c", + "metadata": { + "cq.autogen": "HasDuplicates.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic.lists import HasDuplicates" + ] + }, + { + "cell_type": "markdown", + "id": "eae0fee2", + "metadata": { + "cq.autogen": "HasDuplicates.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "588c7e8a", + "metadata": { + "cq.autogen": "HasDuplicates.has_duplicates_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "n = sympy.Symbol(\"n\")\n", + "has_duplicates_symb = HasDuplicates(4, QUInt(n))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7d63e51", + "metadata": { + "cq.autogen": "HasDuplicates.has_duplicates" + }, + "outputs": [], + "source": [ + "has_duplicates = HasDuplicates(4, QUInt(3))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a12dbdf3", + "metadata": { + "cq.autogen": "HasDuplicates.has_duplicates_symb_len" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "l, n = sympy.symbols(\"l n\")\n", + "has_duplicates_symb_len = HasDuplicates(l, QUInt(n))" + ] + }, + { + "cell_type": "markdown", + "id": "d189b98d", + "metadata": { + "cq.autogen": "HasDuplicates.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b905935f", + "metadata": { + "cq.autogen": "HasDuplicates.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([has_duplicates_symb, has_duplicates, has_duplicates_symb_len],\n", + " ['`has_duplicates_symb`', '`has_duplicates`', '`has_duplicates_symb_len`'])" + ] + }, + { + "cell_type": "markdown", + "id": "2156df99", + "metadata": { + "cq.autogen": "HasDuplicates.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d05f52a0", + "metadata": { + "cq.autogen": "HasDuplicates.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "has_duplicates_symb_g, has_duplicates_symb_sigma = has_duplicates_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(has_duplicates_symb_g)\n", + "show_counts_sigma(has_duplicates_symb_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 03a6eba8c90c08fdbee3e4afc55fa6e04568c982 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Sun, 1 Dec 2024 22:54:42 +0100 Subject: [PATCH 3/3] fix tests --- .../bloqs/arithmetic/lists/has_duplicates.py | 3 +- .../arithmetic/lists/has_duplicates_test.py | 3 +- qualtran/bloqs/arithmetic/lists/lists.ipynb | 92 ++++++------------- .../arithmetic/lists/symmetric_difference.py | 21 +++-- .../lists/symmetric_difference_test.py | 19 ++-- qualtran/bloqs/arithmetic/sorting.py | 2 +- qualtran/conftest.py | 3 + qualtran/serialization/resolver_dict.py | 4 + 8 files changed, 59 insertions(+), 88 deletions(-) diff --git a/qualtran/bloqs/arithmetic/lists/has_duplicates.py b/qualtran/bloqs/arithmetic/lists/has_duplicates.py index cce9c88d2..4da8d2a61 100644 --- a/qualtran/bloqs/arithmetic/lists/has_duplicates.py +++ b/qualtran/bloqs/arithmetic/lists/has_duplicates.py @@ -172,6 +172,5 @@ def _has_duplicates_symb_len() -> HasDuplicates: _HAS_DUPLICATES_DOC = BloqDocSpec( - bloq_cls=HasDuplicates, - examples=[_has_duplicates_symb, _has_duplicates, _has_duplicates_symb_len], + bloq_cls=HasDuplicates, examples=[_has_duplicates_symb, _has_duplicates] ) diff --git a/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py b/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py index 62ef23aa3..0a02fa83a 100644 --- a/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py +++ b/qualtran/bloqs/arithmetic/lists/has_duplicates_test.py @@ -18,8 +18,7 @@ import qualtran.testing as qlt_testing from qualtran import QInt, QUInt - -from .has_duplicates import ( +from qualtran.bloqs.arithmetic.lists.has_duplicates import ( _has_duplicates, _has_duplicates_symb, _has_duplicates_symb_len, diff --git a/qualtran/bloqs/arithmetic/lists/lists.ipynb b/qualtran/bloqs/arithmetic/lists/lists.ipynb index cd9ebf669..2251b288d 100644 --- a/qualtran/bloqs/arithmetic/lists/lists.ipynb +++ b/qualtran/bloqs/arithmetic/lists/lists.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "4b6002bc", + "id": "46306919", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73f38d89", + "id": "47761dec", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "75d1819f", + "id": "19f11879", "metadata": { "cq.autogen": "SortInPlace.bloq_doc.md" }, @@ -79,7 +79,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fcb6c012", + "id": "ce69b6e8", "metadata": { "cq.autogen": "SortInPlace.bloq_doc.py" }, @@ -90,7 +90,7 @@ }, { "cell_type": "markdown", - "id": "e878a52b", + "id": "e84df89b", "metadata": { "cq.autogen": "SymmetricDifference.bloq_doc.md" }, @@ -121,7 +121,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ccaa8a4b", + "id": "d3a9d9ea", "metadata": { "cq.autogen": "SymmetricDifference.bloq_doc.py" }, @@ -132,7 +132,7 @@ }, { "cell_type": "markdown", - "id": "6cefce9f", + "id": "f58e0eba", "metadata": { "cq.autogen": "SymmetricDifference.example_instances.md" }, @@ -143,42 +143,19 @@ { "cell_type": "code", "execution_count": null, - "id": "b7ffc68f", + "id": "29fc34f0", "metadata": { "cq.autogen": "SymmetricDifference.symm_diff" }, "outputs": [], "source": [ - "import sympy\n", - "\n", - "from qualtran.symbolics import bit_length\n", - "\n", - "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", - "dtype = QUInt(bit_length(n - 1))\n", - "symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, dtype=dtype)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4da0b292", - "metadata": { - "cq.autogen": "SymmetricDifference.symm_diff_equal_size" - }, - "outputs": [], - "source": [ - "import sympy\n", - "\n", - "from qualtran.symbolics import bit_length\n", - "\n", - "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", - "dtype = QUInt(bit_length(n - 1))\n", - "symm_diff_equal_size = SymmetricDifference(n_lhs=c * k, n_rhs=c * k, n_diff=k, dtype=dtype)" + "dtype = QUInt(4)\n", + "symm_diff = SymmetricDifference(n_lhs=4, n_rhs=2, n_diff=4, dtype=dtype)" ] }, { "cell_type": "markdown", - "id": "79bb967b", + "id": "70608811", "metadata": { "cq.autogen": "SymmetricDifference.graphical_signature.md" }, @@ -189,20 +166,20 @@ { "cell_type": "code", "execution_count": null, - "id": "fb210e38", + "id": "9df9334c", "metadata": { "cq.autogen": "SymmetricDifference.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([symm_diff, symm_diff_equal_size],\n", - " ['`symm_diff`', '`symm_diff_equal_size`'])" + "show_bloqs([symm_diff],\n", + " ['`symm_diff`'])" ] }, { "cell_type": "markdown", - "id": "946e2cef", + "id": "476c580a", "metadata": { "cq.autogen": "SymmetricDifference.call_graph.md" }, @@ -213,7 +190,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9a25a154", + "id": "a6e6f812", "metadata": { "cq.autogen": "SymmetricDifference.call_graph.py" }, @@ -227,7 +204,7 @@ }, { "cell_type": "markdown", - "id": "a78e7e32", + "id": "15e88a40", "metadata": { "cq.autogen": "HasDuplicates.bloq_doc.md" }, @@ -254,7 +231,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0707263c", + "id": "101f6899", "metadata": { "cq.autogen": "HasDuplicates.bloq_doc.py" }, @@ -265,7 +242,7 @@ }, { "cell_type": "markdown", - "id": "eae0fee2", + "id": "6ceded7e", "metadata": { "cq.autogen": "HasDuplicates.example_instances.md" }, @@ -276,7 +253,7 @@ { "cell_type": "code", "execution_count": null, - "id": "588c7e8a", + "id": "d5f5efa7", "metadata": { "cq.autogen": "HasDuplicates.has_duplicates_symb" }, @@ -291,7 +268,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d7d63e51", + "id": "a5412e0d", "metadata": { "cq.autogen": "HasDuplicates.has_duplicates" }, @@ -300,24 +277,9 @@ "has_duplicates = HasDuplicates(4, QUInt(3))" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "a12dbdf3", - "metadata": { - "cq.autogen": "HasDuplicates.has_duplicates_symb_len" - }, - "outputs": [], - "source": [ - "import sympy\n", - "\n", - "l, n = sympy.symbols(\"l n\")\n", - "has_duplicates_symb_len = HasDuplicates(l, QUInt(n))" - ] - }, { "cell_type": "markdown", - "id": "d189b98d", + "id": "7c1c62b4", "metadata": { "cq.autogen": "HasDuplicates.graphical_signature.md" }, @@ -328,20 +290,20 @@ { "cell_type": "code", "execution_count": null, - "id": "b905935f", + "id": "1198bb5d", "metadata": { "cq.autogen": "HasDuplicates.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([has_duplicates_symb, has_duplicates, has_duplicates_symb_len],\n", - " ['`has_duplicates_symb`', '`has_duplicates`', '`has_duplicates_symb_len`'])" + "show_bloqs([has_duplicates_symb, has_duplicates],\n", + " ['`has_duplicates_symb`', '`has_duplicates`'])" ] }, { "cell_type": "markdown", - "id": "2156df99", + "id": "f6b8cde0", "metadata": { "cq.autogen": "HasDuplicates.call_graph.md" }, @@ -352,7 +314,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d05f52a0", + "id": "75afba07", "metadata": { "cq.autogen": "HasDuplicates.call_graph.py" }, diff --git a/qualtran/bloqs/arithmetic/lists/symmetric_difference.py b/qualtran/bloqs/arithmetic/lists/symmetric_difference.py index 3d18d5689..72d13f822 100644 --- a/qualtran/bloqs/arithmetic/lists/symmetric_difference.py +++ b/qualtran/bloqs/arithmetic/lists/symmetric_difference.py @@ -103,28 +103,33 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: @bloq_example def _symm_diff() -> SymmetricDifference: + dtype = QUInt(4) + symm_diff = SymmetricDifference(n_lhs=4, n_rhs=2, n_diff=4, dtype=dtype) + return symm_diff + + +@bloq_example +def _symm_diff_symb() -> SymmetricDifference: import sympy from qualtran.symbolics import bit_length n, k, c = sympy.symbols("n k c", positive=True, integer=True) dtype = QUInt(bit_length(n - 1)) - symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, dtype=dtype) - return symm_diff + symm_diff_symb = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, dtype=dtype) + return symm_diff_symb @bloq_example -def _symm_diff_equal_size() -> SymmetricDifference: +def _symm_diff_equal_size_symb() -> SymmetricDifference: import sympy from qualtran.symbolics import bit_length n, k, c = sympy.symbols("n k c", positive=True, integer=True) dtype = QUInt(bit_length(n - 1)) - symm_diff_equal_size = SymmetricDifference(n_lhs=c * k, n_rhs=c * k, n_diff=k, dtype=dtype) - return symm_diff_equal_size + symm_diff_equal_size_symb = SymmetricDifference(n_lhs=c * k, n_rhs=c * k, n_diff=k, dtype=dtype) + return symm_diff_equal_size_symb -_SYMMETRIC_DIFFERENCE_DOC = BloqDocSpec( - bloq_cls=SymmetricDifference, examples=[_symm_diff, _symm_diff_equal_size] -) +_SYMMETRIC_DIFFERENCE_DOC = BloqDocSpec(bloq_cls=SymmetricDifference, examples=[_symm_diff]) diff --git a/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py b/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py index fa3765f70..0ad5a356b 100644 --- a/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py +++ b/qualtran/bloqs/arithmetic/lists/symmetric_difference_test.py @@ -15,21 +15,22 @@ import pytest +import qualtran.testing as qlt_testing +from qualtran.bloqs.arithmetic.lists.symmetric_difference import ( + _symm_diff, + _symm_diff_equal_size_symb, + _symm_diff_symb, +) from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost from qualtran.symbolics import ceil, log2 -from .symmetric_difference import _symm_diff, _symm_diff_equal_size - -@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_symb, _symm_diff_equal_size_symb]) def test_examples(bloq_autotester, bloq_ex): - if bloq_autotester.check_name == 'serialize': - pytest.skip() - bloq_autotester(bloq_ex) -@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +@pytest.mark.parametrize("bloq_ex", [_symm_diff_symb, _symm_diff_equal_size_symb]) def test_cost(bloq_ex): bloq = bloq_ex() gc = get_cost_value(bloq, QECGatesCost()) @@ -58,6 +59,4 @@ def test_cost(bloq_ex): @pytest.mark.notebook def test_notebook(): - from qualtran.testing import execute_notebook - - execute_notebook('arithmetic') + qlt_testing.execute_notebook('lists') diff --git a/qualtran/bloqs/arithmetic/sorting.py b/qualtran/bloqs/arithmetic/sorting.py index c500effc9..a6e9937cc 100644 --- a/qualtran/bloqs/arithmetic/sorting.py +++ b/qualtran/bloqs/arithmetic/sorting.py @@ -251,7 +251,7 @@ def build_composite_bloq( k = self.half_length if is_symbolic(k): raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") - if (k & (k - 1)) == 0: + if (k & (k - 1)) != 0: # TODO(#1090) support non-power-of-two input lengths raise DecomposeNotImplementedError("length of input lists must be a power of 2") diff --git a/qualtran/conftest.py b/qualtran/conftest.py index 28d79a89f..439ae6145 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -141,6 +141,9 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'ctrl_on_symbolic_cv', # cannot serialize Shaped 'ctrl_on_symbolic_cv_multi', # cannot serialize Shaped 'ctrl_on_symbolic_n_ctrls', # cannot serialize Shaped + 'has_duplicates_symb_len', # cannot serialize HasLength + 'symm_diff_symb', # round trip fail: sympy assumptions not serialized + 'symm_diff_equal_size_symb', # round trip fail: sympy assumptions not serialized ]: pytest.xfail("Skipping serialization test for bloq examples that cannot yet be serialized.") diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index d81f9030a..83f05c786 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -23,6 +23,7 @@ import qualtran.bloqs.arithmetic.conversions.ones_complement_to_twos_complement import qualtran.bloqs.arithmetic.conversions.sign_extension import qualtran.bloqs.arithmetic.hamming_weight +import qualtran.bloqs.arithmetic.lists import qualtran.bloqs.arithmetic.multiplication import qualtran.bloqs.arithmetic.negate import qualtran.bloqs.arithmetic.permutation @@ -191,6 +192,9 @@ "qualtran.bloqs.arithmetic.conversions.sign_extension.SignExtend": qualtran.bloqs.arithmetic.conversions.sign_extension.SignExtend, "qualtran.bloqs.arithmetic.conversions.sign_extension.SignTruncate": qualtran.bloqs.arithmetic.conversions.sign_extension.SignTruncate, "qualtran.bloqs.arithmetic.hamming_weight.HammingWeightCompute": qualtran.bloqs.arithmetic.hamming_weight.HammingWeightCompute, + "qualtran.bloqs.arithmetic.lists.has_duplicates.HasDuplicates": qualtran.bloqs.arithmetic.lists.has_duplicates.HasDuplicates, + "qualtran.bloqs.arithmetic.lists.sort_in_place.SortInPlace": qualtran.bloqs.arithmetic.lists.sort_in_place.SortInPlace, + "qualtran.bloqs.arithmetic.lists.symmetric_difference.SymmetricDifference": qualtran.bloqs.arithmetic.lists.symmetric_difference.SymmetricDifference, "qualtran.bloqs.arithmetic.multiplication.InvertRealNumber": qualtran.bloqs.arithmetic.multiplication.InvertRealNumber, "qualtran.bloqs.arithmetic.multiplication.MultiplyTwoReals": qualtran.bloqs.arithmetic.multiplication.MultiplyTwoReals, "qualtran.bloqs.arithmetic.multiplication.PlusEqualProduct": qualtran.bloqs.arithmetic.multiplication.PlusEqualProduct,