From 59f674d6d2f445a4e3962df6fcb8cb425eae7c53 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri <7265746+anurudhp@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:25:00 -0700 Subject: [PATCH 1/2] Implement `Negate` (two's complement) (#1144) * implement negate (2s complement) cleanup subtract, shims * add csim test * mypy * examples + bloqdoc * notebook * subtract docstring * explain how negate works for unsigned types, add reference --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 5 + docs/bloqs/index.rst | 1 + qualtran/_infra/data_types.py | 6 +- qualtran/bloqs/arithmetic/__init__.py | 3 +- qualtran/bloqs/arithmetic/_shims.py | 9 - qualtran/bloqs/arithmetic/negate.ipynb | 176 +++++++++++++++++++ qualtran/bloqs/arithmetic/negate.py | 85 +++++++++ qualtran/bloqs/arithmetic/negate_test.py | 44 +++++ qualtran/bloqs/arithmetic/subtraction.ipynb | 3 +- qualtran/bloqs/arithmetic/subtraction.py | 46 ++--- qualtran/bloqs/basic_gates/on_each.py | 13 +- qualtran/bloqs/mod_arithmetic/_shims.py | 6 +- qualtran/serialization/resolver_dict.py | 2 + 13 files changed, 354 insertions(+), 45 deletions(-) create mode 100644 qualtran/bloqs/arithmetic/negate.ipynb create mode 100644 qualtran/bloqs/arithmetic/negate.py create mode 100644 qualtran/bloqs/arithmetic/negate_test.py diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index ea6fd0263a..c1fc35b2ed 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -337,6 +337,11 @@ qualtran.bloqs.arithmetic.addition._ADD_K_DOC, ], ), + NotebookSpecV2( + title='Negation', + module=qualtran.bloqs.arithmetic.negate, + bloq_specs=[qualtran.bloqs.arithmetic.negate._NEGATE_DOC], + ), NotebookSpecV2( title='Subtraction', module=qualtran.bloqs.arithmetic.subtraction, diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index faf4785203..1f05c139c7 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -60,6 +60,7 @@ Bloqs Library :caption: Arithmetic: arithmetic/addition.ipynb + arithmetic/negate.ipynb arithmetic/subtraction.ipynb arithmetic/multiplication.ipynb arithmetic/comparison.ipynb diff --git a/qualtran/_infra/data_types.py b/qualtran/_infra/data_types.py index aa5f6f3136..2272471916 100644 --- a/qualtran/_infra/data_types.py +++ b/qualtran/_infra/data_types.py @@ -221,7 +221,11 @@ def to_bits(self, x: int) -> List[int]: def from_bits(self, bits: Sequence[int]) -> int: """Combine individual bits to form x""" sign = bits[0] - x = QUInt(self.bitsize - 1).from_bits([1 - x if sign else x for x in bits[1:]]) + x = ( + 0 + if self.bitsize == 1 + else QUInt(self.bitsize - 1).from_bits([1 - x if sign else x for x in bits[1:]]) + ) return ~x if sign else x def assert_valid_classical_val(self, val: int, debug_str: str = 'val'): diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index 9cbe1331a8..f4472de114 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -33,7 +33,8 @@ SquareRealNumber, SumOfSquares, ) +from qualtran.bloqs.arithmetic.negate import Negate from qualtran.bloqs.arithmetic.sorting import BitonicSort, Comparator from qualtran.bloqs.arithmetic.subtraction import Subtract -from ._shims import CHalf, Lt, MultiCToffoli, Negate +from ._shims import CHalf, Lt, MultiCToffoli diff --git a/qualtran/bloqs/arithmetic/_shims.py b/qualtran/bloqs/arithmetic/_shims.py index e893549d6f..586fface53 100644 --- a/qualtran/bloqs/arithmetic/_shims.py +++ b/qualtran/bloqs/arithmetic/_shims.py @@ -63,12 +63,3 @@ class CHalf(Bloq): @cached_property def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.n))]) - - -@frozen -class Negate(Bloq): - n: int - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n))]) diff --git a/qualtran/bloqs/arithmetic/negate.ipynb b/qualtran/bloqs/arithmetic/negate.ipynb new file mode 100644 index 0000000000..832bdbaf66 --- /dev/null +++ b/qualtran/bloqs/arithmetic/negate.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2f4b71c0", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Negation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a24a509", + "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": "960bd7ab", + "metadata": { + "cq.autogen": "Negate.bloq_doc.md" + }, + "source": [ + "## `Negate`\n", + "Compute the two's complement negation for a integer/fixed-point value.\n", + "\n", + "This bloq is equivalent to the \"Unary minus\" [1] C++ operator.\n", + "- For a signed `x`, the output is `-x`.\n", + "- For an unsigned `x`, the output is `2^n - x` (where `n` is the bitsize).\n", + "\n", + "This is computed by the bit-fiddling trick `-x = ~x + 1`, as follows:\n", + "1. Flip all the bits (i.e. `x := ~x`)\n", + "2. Add 1 to the value (interpreted as an unsigned integer), ignoring\n", + " any overflow. (i.e. `x := x + 1`)\n", + "\n", + "For a controlled negate bloq: the second step uses a quantum-quantum adder by\n", + "loading the constant (i.e. 1), therefore has an improved controlled version\n", + "which only controls the constant load and not the adder circuit, hence halving\n", + "the T-cost compared to a controlled adder.\n", + "\n", + "#### Parameters\n", + " - `dtype`: The data type of the input value. \n", + "\n", + "#### Registers\n", + " - `x`: Any unsigned value or signed value (in two's complement form). \n", + "\n", + "#### References\n", + " - [Arithmetic Operators - cppreference](https://en.cppreference.com/w/cpp/language/operator_arithmetic). Operator \"Unary Minus\". Last accessed 17 July 2024.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90fc7252", + "metadata": { + "cq.autogen": "Negate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import Negate" + ] + }, + { + "cell_type": "markdown", + "id": "7d7e0b3c", + "metadata": { + "cq.autogen": "Negate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cd8d8b2", + "metadata": { + "cq.autogen": "Negate.negate" + }, + "outputs": [], + "source": [ + "negate = Negate(QInt(8))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3bf4577", + "metadata": { + "cq.autogen": "Negate.negate_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "n = sympy.Symbol(\"n\")\n", + "negate_symb = Negate(QInt(n))" + ] + }, + { + "cell_type": "markdown", + "id": "1617757d", + "metadata": { + "cq.autogen": "Negate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9986a92e", + "metadata": { + "cq.autogen": "Negate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([negate, negate_symb],\n", + " ['`negate`', '`negate_symb`'])" + ] + }, + { + "cell_type": "markdown", + "id": "ab653b84", + "metadata": { + "cq.autogen": "Negate.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "345ef9d4", + "metadata": { + "cq.autogen": "Negate.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "negate_g, negate_sigma = negate.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(negate_g)\n", + "show_counts_sigma(negate_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/arithmetic/negate.py b/qualtran/bloqs/arithmetic/negate.py new file mode 100644 index 0000000000..1a966bf5bd --- /dev/null +++ b/qualtran/bloqs/arithmetic/negate.py @@ -0,0 +1,85 @@ +# 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 TYPE_CHECKING + +from attrs import frozen + +from qualtran import Bloq, bloq_example, BloqDocSpec, QDType, QInt, Signature +from qualtran.bloqs.arithmetic import AddK +from qualtran.bloqs.basic_gates import OnEach, XGate + +if TYPE_CHECKING: + from qualtran import BloqBuilder, SoquetT + + +@frozen +class Negate(Bloq): + """Compute the two's complement negation for a integer/fixed-point value. + + This bloq is equivalent to the "Unary minus" [1] C++ operator. + - For a signed `x`, the output is `-x`. + - For an unsigned `x`, the output is `2^n - x` (where `n` is the bitsize). + + This is computed by the bit-fiddling trick `-x = ~x + 1`, as follows: + 1. Flip all the bits (i.e. `x := ~x`) + 2. Add 1 to the value (interpreted as an unsigned integer), ignoring + any overflow. (i.e. `x := x + 1`) + + For a controlled negate bloq: the second step uses a quantum-quantum adder by + loading the constant (i.e. 1), therefore has an improved controlled version + which only controls the constant load and not the adder circuit, hence halving + the T-cost compared to a controlled adder. + + Args: + dtype: The data type of the input value. + + Registers: + x: Any unsigned value or signed value (in two's complement form). + + References: + [Arithmetic Operators - cppreference](https://en.cppreference.com/w/cpp/language/operator_arithmetic) + Operator "Unary Minus". Last accessed 17 July 2024. + """ + + dtype: QDType + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(x=self.dtype) + + def build_composite_bloq(self, bb: 'BloqBuilder', x: 'SoquetT') -> dict[str, 'SoquetT']: + # x := ~x + x = bb.add(OnEach(self.dtype.num_qubits, XGate()), q=x) + # x := x + 1 + x = bb.add(AddK(self.dtype.num_qubits, k=1), x=x) + return {'x': x} + + +@bloq_example +def _negate() -> Negate: + negate = Negate(QInt(8)) + return negate + + +@bloq_example +def _negate_symb() -> Negate: + import sympy + + n = sympy.Symbol("n") + negate_symb = Negate(QInt(n)) + return negate_symb + + +_NEGATE_DOC = BloqDocSpec(bloq_cls=Negate, examples=[_negate, _negate_symb]) diff --git a/qualtran/bloqs/arithmetic/negate_test.py b/qualtran/bloqs/arithmetic/negate_test.py new file mode 100644 index 0000000000..b6ee06978a --- /dev/null +++ b/qualtran/bloqs/arithmetic/negate_test.py @@ -0,0 +1,44 @@ +# 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 pytest + +from qualtran import QInt, QUInt +from qualtran.bloqs.arithmetic.negate import _negate, _negate_symb, Negate + + +def test_examples(bloq_autotester): + bloq_autotester(_negate) + bloq_autotester(_negate_symb) + + +@pytest.mark.parametrize("bitsize", [1, 2, 3, 4, 8]) +def test_negate_classical_sim(bitsize: int): + # TODO use QInt once classical sim is fixed. + # classical sim currently only supports unsigned. + def _uint_to_int(val: int) -> int: + return QInt(bitsize).from_bits(QUInt(bitsize).to_bits(val)) + + dtype = QUInt(bitsize) + + bloq = Negate(dtype) + for x_unsigned in dtype.get_classical_domain(): + (neg_x_unsigned,) = bloq.call_classically(x=x_unsigned) + x = _uint_to_int(x_unsigned) + neg_x = _uint_to_int(int(neg_x_unsigned)) + if x == -(2 ** (bitsize - 1)): + # twos complement negate(-2**(n - 1)) == -2**(n - 1) + assert neg_x == x + else: + # all other values + assert neg_x == -x diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index 7a77515b34..58c565eb9a 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -40,8 +40,7 @@ "\n", "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a-b\\rangle$ using $4n - 4 T$ gates.\n", "\n", - "This construction uses `XGate` and `AddK` to compute the twos-compliment of `b` before\n", - "doing a standard `Add`.\n", + "This first negates $b$ (assuming a two's complement representation), and then adds $a$ into it.\n", "\n", "#### Parameters\n", " - `a_dtype`: Quantum datatype used to represent the integer a.\n", diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 4884150d86..3db832ce39 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -14,7 +14,6 @@ import math from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union -import numpy as np import sympy from attrs import field, frozen @@ -31,8 +30,7 @@ Soquet, SoquetT, ) -from qualtran.bloqs.arithmetic.addition import Add, AddK -from qualtran.bloqs.basic_gates import XGate +from qualtran.bloqs.arithmetic import Add, Negate from qualtran.drawing import Text if TYPE_CHECKING: @@ -47,8 +45,7 @@ class Subtract(Bloq): Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4 T$ gates. - This construction uses `XGate` and `AddK` to compute the twos-compliment of `b` before - doing a standard `Add`. + This first negates $b$ (assuming a two's complement representation), and then adds $a$ into it. Args: a_dtype: Quantum datatype used to represent the integer a. @@ -95,6 +92,19 @@ def dtype(self): def signature(self): return Signature([Register("a", self.a_dtype), Register("b", self.b_dtype)]) + def _dtype_as_unsigned( + self, dtype: Union[QInt, QUInt, QMontgomeryUInt] + ) -> Union[QUInt, QMontgomeryUInt]: + return dtype if not isinstance(dtype, QInt) else QUInt(dtype.bitsize) + + @property + def a_dtype_as_unsigned(self): + return self._dtype_as_unsigned(self.a_dtype) + + @property + def b_dtype_as_unsigned(self): + return self._dtype_as_unsigned(self.b_dtype) + def on_classical_vals( self, a: 'ClassicalValT', b: 'ClassicalValT' ) -> Dict[str, 'ClassicalValT']: @@ -118,32 +128,14 @@ def wire_symbol( raise ValueError() def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - a_dtype = ( - self.a_dtype if not isinstance(self.a_dtype, QInt) else QUInt(self.a_dtype.bitsize) - ) - b_dtype = ( - self.b_dtype if not isinstance(self.b_dtype, QInt) else QUInt(self.b_dtype.bitsize) - ) return { - (XGate(), self.b_dtype.bitsize), - (AddK(self.b_dtype.bitsize, k=1), 1), - (Add(a_dtype, b_dtype), 1), + (Negate(self.b_dtype), 1), + (Add(self.a_dtype_as_unsigned, self.b_dtype_as_unsigned), 1), } def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: - b = np.array([bb.add(XGate(), q=q) for q in bb.split(b)]) # 1s complement of b. - b = bb.add( - AddK(self.b_dtype.bitsize, k=1), x=bb.join(b, self.b_dtype) - ) # 2s complement of b. - - a_dtype = ( - self.a_dtype if not isinstance(self.a_dtype, QInt) else QUInt(self.a_dtype.bitsize) - ) - b_dtype = ( - self.b_dtype if not isinstance(self.b_dtype, QInt) else QUInt(self.b_dtype.bitsize) - ) - - a, b = bb.add(Add(a_dtype, b_dtype), a=a, b=b) # a - b + b = bb.add(Negate(self.b_dtype), x=b) + a, b = bb.add(Add(self.a_dtype_as_unsigned, self.b_dtype_as_unsigned), a=a, b=b) # a - b return {'a': a, 'b': b} diff --git a/qualtran/bloqs/basic_gates/on_each.py b/qualtran/bloqs/basic_gates/on_each.py index 6f50b08a7a..68fa625329 100644 --- a/qualtran/bloqs/basic_gates/on_each.py +++ b/qualtran/bloqs/basic_gates/on_each.py @@ -19,7 +19,16 @@ import attrs import sympy -from qualtran import Bloq, BloqBuilder, QAny, Register, Signature, Soquet, SoquetT +from qualtran import ( + Bloq, + BloqBuilder, + DecomposeTypeError, + QAny, + Register, + Signature, + Soquet, + SoquetT, +) from qualtran.drawing import Text, WireSymbol from qualtran.drawing.musical_score import TextBox from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -53,7 +62,7 @@ def signature(self) -> Signature: def build_composite_bloq(self, bb: BloqBuilder, *, q: Soquet) -> Dict[str, SoquetT]: if isinstance(self.n, sympy.Expr): - raise ValueError(f'Symbolic n not allowed {self.n}') + raise DecomposeTypeError(f'Cannote decompose {self} with symbolic bitsize {self.n}') qs = bb.split(q) for i in range(self.n): qs[i] = bb.add(self.gate, q=qs[i]) diff --git a/qualtran/bloqs/mod_arithmetic/_shims.py b/qualtran/bloqs/mod_arithmetic/_shims.py index c4c794776a..6b831963e9 100644 --- a/qualtran/bloqs/mod_arithmetic/_shims.py +++ b/qualtran/bloqs/mod_arithmetic/_shims.py @@ -27,8 +27,8 @@ from attrs import frozen from qualtran import Bloq, QBit, QUInt, Register, Signature -from qualtran.bloqs.arithmetic import Add, AddK, Subtract -from qualtran.bloqs.arithmetic._shims import CHalf, Lt, MultiCToffoli, Negate +from qualtran.bloqs.arithmetic import Add, AddK, Negate, Subtract +from qualtran.bloqs.arithmetic._shims import CHalf, Lt, MultiCToffoli from qualtran.bloqs.basic_gates import CNOT, CSwap, Swap, Toffoli from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.symbolics import ceil, log2 @@ -159,7 +159,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: # return {(Toffoli(), 32 * self.n**2 * log2(self.n))} return { (_ModInvInner(n=self.n, mod=self.mod), 2 * self.n), - (Negate(self.n), 1), + (Negate(QUInt(self.n)), 1), (AddK(self.n, k=self.mod), 1), (Swap(self.n), 1), } diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 5b8b3f3701..e458cb429a 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -20,6 +20,7 @@ import qualtran.bloqs.arithmetic.conversions import qualtran.bloqs.arithmetic.hamming_weight import qualtran.bloqs.arithmetic.multiplication +import qualtran.bloqs.arithmetic.negate import qualtran.bloqs.arithmetic.permutation import qualtran.bloqs.arithmetic.sorting import qualtran.bloqs.basic_gates.cnot @@ -155,6 +156,7 @@ "qualtran.bloqs.arithmetic.multiplication.Square": qualtran.bloqs.arithmetic.multiplication.Square, "qualtran.bloqs.arithmetic.multiplication.SquareRealNumber": qualtran.bloqs.arithmetic.multiplication.SquareRealNumber, "qualtran.bloqs.arithmetic.multiplication.SumOfSquares": qualtran.bloqs.arithmetic.multiplication.SumOfSquares, + "qualtran.bloqs.arithmetic.negate.Negate": qualtran.bloqs.arithmetic.negate.Negate, "qualtran.bloqs.arithmetic.permutation.Permutation": qualtran.bloqs.arithmetic.permutation.Permutation, "qualtran.bloqs.arithmetic.permutation.PermutationCycle": qualtran.bloqs.arithmetic.permutation.PermutationCycle, "qualtran.bloqs.arithmetic.sorting.BitonicMerge": qualtran.bloqs.arithmetic.sorting.BitonicMerge, From 7f3e92959b497084f2bf9d033326490423154557 Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Wed, 17 Jul 2024 14:11:52 -0700 Subject: [PATCH 2/2] Add `Xor` bloq (#1149) * Add `Copy` bloq * Change to `bitwise.Xor` * Address feedback * Address feedback * Update notebook * Update test --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 6 + docs/bloqs/index.rst | 1 + qualtran/bloqs/arithmetic/__init__.py | 1 + qualtran/bloqs/arithmetic/bitwise.ipynb | 160 +++++++++++++++++++ qualtran/bloqs/arithmetic/bitwise.py | 67 ++++++++ qualtran/bloqs/arithmetic/bitwise_test.py | 37 ++++- qualtran/serialization/resolver_dict.py | 1 + 7 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 qualtran/bloqs/arithmetic/bitwise.ipynb diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index c1fc35b2ed..f436cb4479 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -53,6 +53,7 @@ from qualtran_dev_tools.jupyter_autogen import NotebookSpecV2, render_notebook import qualtran.bloqs.arithmetic.addition +import qualtran.bloqs.arithmetic.bitwise import qualtran.bloqs.arithmetic.permutation import qualtran.bloqs.arithmetic.sorting import qualtran.bloqs.arithmetic.subtraction @@ -400,6 +401,11 @@ qualtran.bloqs.arithmetic.permutation._PERMUTATION_CYCLE_DOC, ], ), + NotebookSpecV2( + title='Bitwise Operations', + module=qualtran.bloqs.arithmetic.bitwise, + bloq_specs=[qualtran.bloqs.arithmetic.bitwise._XOR_DOC], + ), ] MOD_ARITHMETIC = [ diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 1f05c139c7..2a5af961cb 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -67,6 +67,7 @@ Bloqs Library arithmetic/sorting.ipynb arithmetic/conversions.ipynb arithmetic/permutation.ipynb + arithmetic/bitwise.ipynb .. toctree:: :maxdepth: 2 diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index f4472de114..00b016ed55 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. from qualtran.bloqs.arithmetic.addition import Add, AddK, OutOfPlaceAdder +from qualtran.bloqs.arithmetic.bitwise import Xor, XorK from qualtran.bloqs.arithmetic.comparison import ( BiQubitsMixer, EqualsAConstant, diff --git a/qualtran/bloqs/arithmetic/bitwise.ipynb b/qualtran/bloqs/arithmetic/bitwise.ipynb new file mode 100644 index 0000000000..357f2543ea --- /dev/null +++ b/qualtran/bloqs/arithmetic/bitwise.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "046f6195", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Bitwise Operations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7fb03a9", + "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": "18fe7319", + "metadata": { + "cq.autogen": "Xor.bloq_doc.md" + }, + "source": [ + "## `Xor`\n", + "Xor the value of one register into another via CNOTs.\n", + "\n", + "When both registers are in computational basis and the destination is 0,\n", + "effectively copies the value of the source into the destination.\n", + "\n", + "#### Parameters\n", + " - `dtype`: Data type of the input registers `x` and `y`. \n", + "\n", + "#### Registers\n", + " - `x`: The source register.\n", + " - `y`: The target register.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9304ba8f", + "metadata": { + "cq.autogen": "Xor.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import Xor" + ] + }, + { + "cell_type": "markdown", + "id": "0432ed40", + "metadata": { + "cq.autogen": "Xor.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55b01f5d", + "metadata": { + "cq.autogen": "Xor.xor" + }, + "outputs": [], + "source": [ + "xor = Xor(QAny(4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b8090ff", + "metadata": { + "cq.autogen": "Xor.xor_symb" + }, + "outputs": [], + "source": [ + "xor_symb = Xor(QAny(sympy.Symbol(\"n\")))" + ] + }, + { + "cell_type": "markdown", + "id": "45b7826f", + "metadata": { + "cq.autogen": "Xor.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "320f183d", + "metadata": { + "cq.autogen": "Xor.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([xor, xor_symb],\n", + " ['`xor`', '`xor_symb`'])" + ] + }, + { + "cell_type": "markdown", + "id": "b17250e4", + "metadata": { + "cq.autogen": "Xor.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ff8ffa9", + "metadata": { + "cq.autogen": "Xor.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "xor_g, xor_sigma = xor.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(xor_g)\n", + "show_counts_sigma(xor_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/arithmetic/bitwise.py b/qualtran/bloqs/arithmetic/bitwise.py index 3f80b01557..58397a4cbd 100644 --- a/qualtran/bloqs/arithmetic/bitwise.py +++ b/qualtran/bloqs/arithmetic/bitwise.py @@ -15,12 +15,16 @@ from typing import cast, Dict, Optional, Set, Tuple, TYPE_CHECKING import numpy as np +import sympy from attrs import frozen from qualtran import ( + Bloq, bloq_example, BloqBuilder, + BloqDocSpec, DecomposeTypeError, + QAny, QBit, QDType, QUInt, @@ -36,6 +40,7 @@ if TYPE_CHECKING: from qualtran.resource_counting import BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT def _cvs_converter(vv): @@ -58,6 +63,7 @@ class XorK(SpecializedSingleQubitControlledGate): 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 @@ -118,3 +124,64 @@ def _cxork() -> XorK: cxork = XorK(QUInt(8), 0b01010111).controlled() assert isinstance(cxork, XorK) return cxork + + +@frozen +class Xor(Bloq): + """Xor the value of one register into another via CNOTs. + + When both registers are in computational basis and the destination is 0, + effectively copies the value of the source into the destination. + + Args: + dtype: Data type of the input registers `x` and `y`. + + Registers: + x: The source register. + y: The target register. + """ + + dtype: QDType + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes(x=self.dtype, y=self.dtype) + + def build_composite_bloq(self, bb: BloqBuilder, x: Soquet, y: Soquet) -> Dict[str, SoquetT]: + if not isinstance(self.dtype.num_qubits, int): + raise DecomposeTypeError("`dtype.num_qubits` must be a concrete value.") + + xs = bb.split(x) + ys = bb.split(y) + + for i in range(len(xs)): + xs[i], ys[i] = bb.add_t(CNOT(), ctrl=xs[i], target=ys[i]) + + return {'x': bb.join(xs, dtype=self.dtype), 'y': bb.join(ys, dtype=self.dtype)} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + return {(CNOT(), self.dtype.num_qubits)} + + def on_classical_vals( + self, x: 'ClassicalValT', y: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + return {'x': x, 'y': x ^ y} + + +@bloq_example +def _xor() -> Xor: + xor = Xor(QAny(4)) + return xor + + +@bloq_example +def _xor_symb() -> Xor: + xor_symb = Xor(QAny(sympy.Symbol("n"))) + return xor_symb + + +_XOR_DOC = BloqDocSpec( + bloq_cls=Xor, + import_line='from qualtran.bloqs.arithmetic import Xor', + examples=(_xor, _xor_symb), +) diff --git a/qualtran/bloqs/arithmetic/bitwise_test.py b/qualtran/bloqs/arithmetic/bitwise_test.py index 6dbd024738..b3579afa08 100644 --- a/qualtran/bloqs/arithmetic/bitwise_test.py +++ b/qualtran/bloqs/arithmetic/bitwise_test.py @@ -11,8 +11,13 @@ # 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 + +import numpy as np +import pytest + +from qualtran import BloqBuilder, QAny, QUInt +from qualtran.bloqs.arithmetic.bitwise import _cxork, _xor, _xor_symb, _xork, Xor, XorK +from qualtran.bloqs.basic_gates import IntEffect, IntState def test_examples(bloq_autotester): @@ -36,3 +41,31 @@ def test_xork_classical_sim(): ctrl_out, x_out = cbloq.call_classically(ctrl=1, x=x) assert ctrl_out == 1 assert x_out == x ^ k + + +def test_xor(bloq_autotester): + bloq_autotester(_xor) + + +def test_xor_symb(bloq_autotester): + bloq_autotester(_xor_symb) + + +@pytest.mark.parametrize("dtype", [QAny(4), QUInt(4)]) +@pytest.mark.parametrize("x", range(16)) +@pytest.mark.parametrize("y", range(16)) +def test_xor_call(dtype, x, y): + bloq = Xor(dtype) + x_out, y_out = bloq.call_classically(x=x, y=y) + assert x_out == x and y_out == x ^ y + x_out, y_out = bloq.decompose_bloq().call_classically(x=x, y=y) + assert x_out == x and y_out == x ^ y + + bb = BloqBuilder() + x_soq = bb.add(IntState(x, 4)) + y_soq = bb.add(IntState(y, 4)) + x_soq, y_soq = bb.add_t(bloq, x=x_soq, y=y_soq) + bb.add(IntEffect(x, 4), val=x_soq) + bloq = bb.finalize(y=y_soq) + + np.testing.assert_allclose(bloq.tensor_contract(), IntState(x ^ y, 4).tensor_contract()) diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index e458cb429a..5bd681f29e 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -137,6 +137,7 @@ "qualtran.bloqs.arithmetic.addition.Add": qualtran.bloqs.arithmetic.addition.Add, "qualtran.bloqs.arithmetic.addition.OutOfPlaceAdder": qualtran.bloqs.arithmetic.addition.OutOfPlaceAdder, "qualtran.bloqs.arithmetic.addition.AddK": qualtran.bloqs.arithmetic.AddK, + "qualtran.bloqs.arithmetic.bitwise.Xor": qualtran.bloqs.arithmetic.bitwise.Xor, "qualtran.bloqs.arithmetic.bitwise.XorK": qualtran.bloqs.arithmetic.bitwise.XorK, "qualtran.bloqs.arithmetic.comparison.BiQubitsMixer": qualtran.bloqs.arithmetic.comparison.BiQubitsMixer, "qualtran.bloqs.arithmetic.comparison.EqualsAConstant": qualtran.bloqs.arithmetic.comparison.EqualsAConstant,