From a837d061f5c10a02e26a5dfa52df02b041adcdef Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Thu, 18 Jul 2024 23:59:22 -0700 Subject: [PATCH 1/9] Add `InvertRealNumber` bloq (#1151) * Add `InvertRealNumber` bloq * Address feedback * Fix circular dependency --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 1 + qualtran/bloqs/arithmetic/__init__.py | 1 + .../bloqs/arithmetic/multiplication.ipynb | 111 ++++++++++++++++++ qualtran/bloqs/arithmetic/multiplication.py | 77 +++++++++++- .../bloqs/arithmetic/multiplication_test.py | 31 ++++- qualtran/bloqs/arithmetic/subtraction.py | 3 +- qualtran/serialization/resolver_dict.py | 2 + 7 files changed, 221 insertions(+), 5 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index f436cb447..1d0a38d2a 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -359,6 +359,7 @@ qualtran.bloqs.arithmetic.multiplication._SCALE_INT_BY_REAL_DOC, qualtran.bloqs.arithmetic.multiplication._MULTIPLY_TWO_REALS_DOC, qualtran.bloqs.arithmetic.multiplication._SQUARE_REAL_NUMBER_DOC, + qualtran.bloqs.arithmetic.multiplication._INVERT_REAL_NUMBER_DOC, ], ), NotebookSpecV2( diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index 00b016ed5..e31f8f6ab 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -26,6 +26,7 @@ from qualtran.bloqs.arithmetic.conversions import SignedIntegerToTwosComplement, ToContiguousIndex from qualtran.bloqs.arithmetic.hamming_weight import HammingWeightCompute from qualtran.bloqs.arithmetic.multiplication import ( + InvertRealNumber, MultiplyTwoReals, PlusEqualProduct, Product, diff --git a/qualtran/bloqs/arithmetic/multiplication.ipynb b/qualtran/bloqs/arithmetic/multiplication.ipynb index f5926df6e..c5beeeda8 100644 --- a/qualtran/bloqs/arithmetic/multiplication.ipynb +++ b/qualtran/bloqs/arithmetic/multiplication.ipynb @@ -788,6 +788,117 @@ "show_bloqs([plus_equal_product],\n", " ['`plus_equal_product`'])" ] + }, + { + "cell_type": "markdown", + "id": "2be05cf8", + "metadata": { + "cq.autogen": "InvertRealNumber.bloq_doc.md" + }, + "source": [ + "## `InvertRealNumber`\n", + "Invert a fixed-point representation of a real number.\n", + "\n", + "Implements the unitary:\n", + "$$\n", + " |a\\rangle|0\\rangle \\rightarrow |a\\rangle|1/a\\rangle\n", + "$$\n", + "where $a \\ge 1$.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: Number of bits used to represent the number.\n", + " - `num_frac`: Number of fraction bits in the number. \n", + "\n", + "#### Registers\n", + " - `a`: `bitsize`-sized input register.\n", + " - `result`: `bitsize`-sized output register. \n", + " - `References`: \n", + " - `[Quantum Algorithms and Circuits for Scientific Computing](`: \n", + " - `https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "587e4a5f", + "metadata": { + "cq.autogen": "InvertRealNumber.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import InvertRealNumber" + ] + }, + { + "cell_type": "markdown", + "id": "feabf392", + "metadata": { + "cq.autogen": "InvertRealNumber.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc402a6a", + "metadata": { + "cq.autogen": "InvertRealNumber.invert_real_number" + }, + "outputs": [], + "source": [ + "invert_real_number = InvertRealNumber(bitsize=10, num_frac=7)" + ] + }, + { + "cell_type": "markdown", + "id": "9a600ad4", + "metadata": { + "cq.autogen": "InvertRealNumber.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "608833cb", + "metadata": { + "cq.autogen": "InvertRealNumber.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([invert_real_number],\n", + " ['`invert_real_number`'])" + ] + }, + { + "cell_type": "markdown", + "id": "20751180", + "metadata": { + "cq.autogen": "InvertRealNumber.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62cd4ee3", + "metadata": { + "cq.autogen": "InvertRealNumber.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "invert_real_number_g, invert_real_number_sigma = invert_real_number.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(invert_real_number_g)\n", + "show_counts_sigma(invert_real_number_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/arithmetic/multiplication.py b/qualtran/bloqs/arithmetic/multiplication.py index 99d6b3896..33b85e712 100644 --- a/qualtran/bloqs/arithmetic/multiplication.py +++ b/qualtran/bloqs/arithmetic/multiplication.py @@ -30,9 +30,10 @@ Side, Signature, ) -from qualtran.bloqs.basic_gates import TGate, Toffoli -from qualtran.symbolics import smax -from qualtran.symbolics.types import SymbolicInt +from qualtran.bloqs.arithmetic.subtraction import Subtract +from qualtran.bloqs.basic_gates import CNOT, TGate, Toffoli, XGate +from qualtran.bloqs.mcmt import MultiControlPauli +from qualtran.symbolics import HasLength, smax, SymbolicInt if TYPE_CHECKING: import quimb.tensor as qtn @@ -495,3 +496,73 @@ def _square_real_number() -> SquareRealNumber: _SQUARE_REAL_NUMBER_DOC = BloqDocSpec(bloq_cls=SquareRealNumber, examples=[_square_real_number]) + + +@frozen +class InvertRealNumber(Bloq): + r"""Invert a fixed-point representation of a real number. + + Implements the unitary: + $$ + |a\rangle|0\rangle \rightarrow |a\rangle|1/a\rangle + $$ + where $a \ge 1$. + + Args: + bitsize: Number of bits used to represent the number. + num_frac: Number of fraction bits in the number. + + Registers: + a: `bitsize`-sized input register. + result: `bitsize`-sized output register. + + References: + [Quantum Algorithms and Circuits for Scientific Computing](https://arxiv.org/pdf/1511.08253). Section 2.1. + """ + + bitsize: int + num_frac: int + + def __attrs_post_init__(self): + if self.num_frac == self.bitsize: + raise ValueError("num_frac must be < bitsize since a >= 1.") + + @property + def signature(self): + return Signature( + [ + Register("a", QFxp(self.bitsize, self.num_frac)), + Register("result", QFxp(self.bitsize, self.num_frac)), + ] + ) + + def pretty_name(self) -> str: + return "1/a" + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + # initial approximation: Figure 4 + num_int = self.bitsize - self.num_frac + # Newton-Raphson: Eq. (1) + # x' = -a * x^2 + 2 * x + num_iters = int(np.ceil(np.log2(self.bitsize))) + # TODO: When decomposing we will potentially need to use larger registers. + # Related issue: https://github.com/quantumlib/Qualtran/issues/655 + return { + (Toffoli(), num_int - 1), + (CNOT(), 2 + num_int - 1), + (MultiControlPauli(cvs=HasLength(num_int), target_gate=cirq.X), 1), + (XGate(), 1), + (SquareRealNumber(self.bitsize), num_iters), # x^2 + (MultiplyTwoReals(self.bitsize), num_iters), # a * x^2 + (ScaleIntByReal(self.bitsize, 2), num_iters), # 2 * x + (Subtract(QUInt(self.bitsize)), num_iters), # 2 * x - a * x^2 + } + + +@bloq_example +def _invert_real_number() -> InvertRealNumber: + invert_real_number = InvertRealNumber(bitsize=10, num_frac=7) + return invert_real_number + + +_INVERT_REAL_NUMBER_DOC = BloqDocSpec(bloq_cls=InvertRealNumber, examples=[_invert_real_number]) diff --git a/qualtran/bloqs/arithmetic/multiplication_test.py b/qualtran/bloqs/arithmetic/multiplication_test.py index 0f43bc151..791182778 100644 --- a/qualtran/bloqs/arithmetic/multiplication_test.py +++ b/qualtran/bloqs/arithmetic/multiplication_test.py @@ -11,11 +11,14 @@ # 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 cirq +import numpy as np import pytest from qualtran import BloqBuilder, QUInt, Register from qualtran.bloqs.arithmetic.multiplication import ( + _invert_real_number, _multiply_two_reals, _plus_equal_product, _product, @@ -31,8 +34,11 @@ SquareRealNumber, SumOfSquares, ) -from qualtran.bloqs.basic_gates import IntState +from qualtran.bloqs.arithmetic.subtraction import Subtract +from qualtran.bloqs.basic_gates import CNOT, IntState, Toffoli, XGate +from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlPauli from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity +from qualtran.symbolics import HasLength from qualtran.testing import execute_notebook @@ -64,6 +70,10 @@ def test_plus_equals_product_auto(bloq_autotester): bloq_autotester(_plus_equal_product) +def test_invert_real_number_auto(bloq_autotester): + bloq_autotester(_invert_real_number) + + def test_square(): bb = BloqBuilder() bitsize = 4 @@ -166,6 +176,25 @@ def test_plus_equal_product(): assert t_complexity(bloq) == TComplexity(t=8 * max(a_bit, b_bit) ** 2) +def test_invert_real_number(): + bitsize = 10 + num_frac = 7 + num_int = bitsize - num_frac + num_iters = int(np.ceil(np.log2(bitsize))) + bloq = _invert_real_number() + cost = ( + Toffoli().t_complexity() * (num_int - 1) + + CNOT().t_complexity() * (2 + num_int - 1) + + MultiControlPauli(cvs=HasLength(num_int), target_gate=cirq.X).t_complexity() + + XGate().t_complexity() + + num_iters * SquareRealNumber(bitsize).t_complexity() + + num_iters * MultiplyTwoReals(bitsize).t_complexity() + + num_iters * ScaleIntByReal(bitsize, 2).t_complexity() + + num_iters * Subtract(QUInt(bitsize)).t_complexity() + ) + assert bloq.t_complexity() == cost + + @pytest.mark.notebook def test_multiplication_notebook(): execute_notebook('multiplication') diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 3db832ce3..88a392154 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -30,7 +30,8 @@ Soquet, SoquetT, ) -from qualtran.bloqs.arithmetic import Add, Negate +from qualtran.bloqs.arithmetic.addition import Add +from qualtran.bloqs.arithmetic.negate import Negate from qualtran.drawing import Text if TYPE_CHECKING: diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index b7829e3c6..36393d34f 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -160,6 +160,7 @@ "qualtran.bloqs.arithmetic.conversions.SignedIntegerToTwosComplement": qualtran.bloqs.arithmetic.conversions.SignedIntegerToTwosComplement, "qualtran.bloqs.arithmetic.conversions.ToContiguousIndex": qualtran.bloqs.arithmetic.conversions.ToContiguousIndex, "qualtran.bloqs.arithmetic.hamming_weight.HammingWeightCompute": qualtran.bloqs.arithmetic.hamming_weight.HammingWeightCompute, + "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, "qualtran.bloqs.arithmetic.multiplication.Product": qualtran.bloqs.arithmetic.multiplication.Product, @@ -174,6 +175,7 @@ "qualtran.bloqs.arithmetic.sorting.BitonicSort": qualtran.bloqs.arithmetic.sorting.BitonicSort, "qualtran.bloqs.arithmetic.sorting.Comparator": qualtran.bloqs.arithmetic.sorting.Comparator, "qualtran.bloqs.arithmetic.sorting.ParallelComparators": qualtran.bloqs.arithmetic.sorting.ParallelComparators, + "qualtran.bloqs.arithmetic.subtraction.Subtract": qualtran.bloqs.arithmetic.subtraction.Subtract, "qualtran.bloqs.basic_gates.cnot.CNOT": qualtran.bloqs.basic_gates.cnot.CNOT, "qualtran.bloqs.basic_gates.identity.Identity": qualtran.bloqs.basic_gates.identity.Identity, "qualtran.bloqs.basic_gates.global_phase.GlobalPhase": qualtran.bloqs.basic_gates.global_phase.GlobalPhase, From 85c1dabbcc08c2bd2d3fe4bf154c3fc508430a31 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri <7265746+anurudhp@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:28:10 -0700 Subject: [PATCH 2/9] Add `BitwiseNot` (#1161) * Add `BitwiseNot` * use BitwiseNot in Negate. --------- Co-authored-by: Charles Yuan --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 5 +- qualtran/bloqs/arithmetic/__init__.py | 2 +- qualtran/bloqs/arithmetic/bitwise.ipynb | 113 +++++++++++++++++++ qualtran/bloqs/arithmetic/bitwise.py | 52 ++++++++- qualtran/bloqs/arithmetic/bitwise_test.py | 44 +++++++- qualtran/bloqs/arithmetic/negate.py | 8 +- qualtran/serialization/resolver_dict.py | 1 + 7 files changed, 216 insertions(+), 9 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 1d0a38d2a..ee9023462 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -405,7 +405,10 @@ NotebookSpecV2( title='Bitwise Operations', module=qualtran.bloqs.arithmetic.bitwise, - bloq_specs=[qualtran.bloqs.arithmetic.bitwise._XOR_DOC], + bloq_specs=[ + qualtran.bloqs.arithmetic.bitwise._XOR_DOC, + qualtran.bloqs.arithmetic.bitwise._BITWISE_NOT_DOC, + ], ), ] diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index e31f8f6ab..2342842e5 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -13,7 +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.bitwise import BitwiseNot, Xor, XorK from qualtran.bloqs.arithmetic.comparison import ( BiQubitsMixer, EqualsAConstant, diff --git a/qualtran/bloqs/arithmetic/bitwise.ipynb b/qualtran/bloqs/arithmetic/bitwise.ipynb index 357f2543e..acf27a280 100644 --- a/qualtran/bloqs/arithmetic/bitwise.ipynb +++ b/qualtran/bloqs/arithmetic/bitwise.ipynb @@ -143,6 +143,119 @@ "show_call_graph(xor_g)\n", "show_counts_sigma(xor_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "e31daefa", + "metadata": { + "cq.autogen": "BitwiseNot.bloq_doc.md" + }, + "source": [ + "## `BitwiseNot`\n", + "Flips every bit of the input register.\n", + "\n", + "#### Parameters\n", + " - `dtype`: Data type of the input register `x`. \n", + "\n", + "#### Registers\n", + " - `x`: A quantum register of type `self.dtype`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5c8e8f0", + "metadata": { + "cq.autogen": "BitwiseNot.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic.bitwise import BitwiseNot" + ] + }, + { + "cell_type": "markdown", + "id": "17f9af3e", + "metadata": { + "cq.autogen": "BitwiseNot.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "701b7629", + "metadata": { + "cq.autogen": "BitwiseNot.bitwise_not" + }, + "outputs": [], + "source": [ + "bitwise_not = BitwiseNot(QUInt(4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76d2c35f", + "metadata": { + "cq.autogen": "BitwiseNot.bitwise_not_symb" + }, + "outputs": [], + "source": [ + "n = sympy.Symbol(\"n\")\n", + "bitwise_not_symb = BitwiseNot(QUInt(n))" + ] + }, + { + "cell_type": "markdown", + "id": "b68ac0d3", + "metadata": { + "cq.autogen": "BitwiseNot.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2831aab5", + "metadata": { + "cq.autogen": "BitwiseNot.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([bitwise_not, bitwise_not_symb],\n", + " ['`bitwise_not`', '`bitwise_not_symb`'])" + ] + }, + { + "cell_type": "markdown", + "id": "7ad37a4d", + "metadata": { + "cq.autogen": "BitwiseNot.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b4c0241", + "metadata": { + "cq.autogen": "BitwiseNot.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "bitwise_not_g, bitwise_not_sigma = bitwise_not.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(bitwise_not_g)\n", + "show_counts_sigma(bitwise_not_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/arithmetic/bitwise.py b/qualtran/bloqs/arithmetic/bitwise.py index 38074fe66..81a6b6eb5 100644 --- a/qualtran/bloqs/arithmetic/bitwise.py +++ b/qualtran/bloqs/arithmetic/bitwise.py @@ -32,7 +32,7 @@ Soquet, SoquetT, ) -from qualtran.bloqs.basic_gates import CNOT, XGate +from qualtran.bloqs.basic_gates import CNOT, OnEach, XGate from qualtran.drawing import TextBox, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import is_symbolic, SymbolicInt @@ -183,3 +183,53 @@ def _xor_symb() -> Xor: import_line='from qualtran.bloqs.arithmetic import Xor', examples=(_xor, _xor_symb), ) + + +@frozen +class BitwiseNot(Bloq): + r"""Flips every bit of the input register. + + Args: + dtype: Data type of the input register `x`. + + Registers: + x: A quantum register of type `self.dtype`. + """ + + dtype: QDType + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(x=self.dtype) + + def build_composite_bloq(self, bb: 'BloqBuilder', x: 'Soquet') -> dict[str, 'SoquetT']: + x = bb.add(OnEach(self.dtype.num_qubits, XGate()), q=x) + return {'x': x} + + def wire_symbol( + self, reg: Optional['Register'], idx: tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return TextBox("") + + return TextBox("~x") + + +@bloq_example +def _bitwise_not() -> BitwiseNot: + bitwise_not = BitwiseNot(QUInt(4)) + return bitwise_not + + +@bloq_example +def _bitwise_not_symb() -> BitwiseNot: + n = sympy.Symbol("n") + bitwise_not_symb = BitwiseNot(QUInt(n)) + return bitwise_not_symb + + +_BITWISE_NOT_DOC = BloqDocSpec( + bloq_cls=BitwiseNot, + import_line='from qualtran.bloqs.arithmetic.bitwise import BitwiseNot', + examples=(_bitwise_not, _bitwise_not_symb), +) diff --git a/qualtran/bloqs/arithmetic/bitwise_test.py b/qualtran/bloqs/arithmetic/bitwise_test.py index c8db67087..b95cd45d9 100644 --- a/qualtran/bloqs/arithmetic/bitwise_test.py +++ b/qualtran/bloqs/arithmetic/bitwise_test.py @@ -16,7 +16,16 @@ import pytest from qualtran import BloqBuilder, QAny, QUInt -from qualtran.bloqs.arithmetic.bitwise import _xor, _xor_symb, _xork, Xor, XorK +from qualtran.bloqs.arithmetic.bitwise import ( + _bitwise_not, + _bitwise_not_symb, + _xor, + _xor_symb, + _xork, + BitwiseNot, + Xor, + XorK, +) from qualtran.bloqs.basic_gates import IntEffect, IntState @@ -130,3 +139,36 @@ def test_xor_diagram(): y3: ───x⊕y─── ''', ) + + +def test_bitwise_not_examples(bloq_autotester): + bloq_autotester(_bitwise_not) + bloq_autotester(_bitwise_not_symb) + + +@pytest.mark.parametrize("n", [4, 5]) +def test_bitwise_not_tensor(n): + bloq = BitwiseNot(QUInt(n)) + + matrix = np.zeros((2**n, 2**n)) + for i in range(2**n): + matrix[i, ~i] = 1 + + np.testing.assert_allclose(bloq.tensor_contract(), matrix) + + +def test_bitwise_not_diagram(): + bloq = BitwiseNot(QUInt(4)) + circuit = bloq.as_composite_bloq().to_cirq_circuit() + cirq.testing.assert_has_diagram( + circuit, + ''' +x0: ───~x─── + │ +x1: ───~x─── + │ +x2: ───~x─── + │ +x3: ───~x─── + ''', + ) diff --git a/qualtran/bloqs/arithmetic/negate.py b/qualtran/bloqs/arithmetic/negate.py index 1a966bf5b..bfd36a63e 100644 --- a/qualtran/bloqs/arithmetic/negate.py +++ b/qualtran/bloqs/arithmetic/negate.py @@ -18,7 +18,7 @@ from qualtran import Bloq, bloq_example, BloqDocSpec, QDType, QInt, Signature from qualtran.bloqs.arithmetic import AddK -from qualtran.bloqs.basic_gates import OnEach, XGate +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot if TYPE_CHECKING: from qualtran import BloqBuilder, SoquetT @@ -60,10 +60,8 @@ 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) + x = bb.add(BitwiseNot(self.dtype), x=x) # ~x + x = bb.add(AddK(self.dtype.num_qubits, k=1), x=x) # -x return {'x': x} diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 36393d34f..990250325 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -147,6 +147,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.BitwiseNot": qualtran.bloqs.arithmetic.bitwise.BitwiseNot, "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, From 22b3d5e2062610e908484e46d04771cc25aba56c Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Fri, 19 Jul 2024 10:56:03 -0700 Subject: [PATCH 3/9] Add `SubtractFrom` to subtract from a register in place (#1158) * Add `SubtractFrom` to subtract from a register in place * Update docstring * Change output to |a>|b-a> * Update subtraction.py Co-authored-by: Anurudh Peduri <7265746+anurudhp@users.noreply.github.com> * Update docstring --------- Co-authored-by: Anurudh Peduri <7265746+anurudhp@users.noreply.github.com> --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 5 +- qualtran/bloqs/arithmetic/__init__.py | 2 +- qualtran/bloqs/arithmetic/subtraction.ipynb | 134 ++++++++++++++++++ qualtran/bloqs/arithmetic/subtraction.py | 87 +++++++++++- qualtran/bloqs/arithmetic/subtraction_test.py | 59 +++++++- qualtran/serialization/resolver_dict.py | 2 + 6 files changed, 282 insertions(+), 7 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index ee9023462..a6cb46a7a 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -346,7 +346,10 @@ NotebookSpecV2( title='Subtraction', module=qualtran.bloqs.arithmetic.subtraction, - bloq_specs=[qualtran.bloqs.arithmetic.subtraction._SUB_DOC], + bloq_specs=[ + qualtran.bloqs.arithmetic.subtraction._SUB_DOC, + qualtran.bloqs.arithmetic.subtraction._SUB_FROM_DOC, + ], ), NotebookSpecV2( title='Multiplication', diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index 2342842e5..101369e17 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -37,6 +37,6 @@ ) from qualtran.bloqs.arithmetic.negate import Negate from qualtran.bloqs.arithmetic.sorting import BitonicSort, Comparator -from qualtran.bloqs.arithmetic.subtraction import Subtract +from qualtran.bloqs.arithmetic.subtraction import Subtract, SubtractFrom from ._shims import CHalf, Lt, MultiCToffoli diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index 58c565eb9..d7e5c66f3 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -170,6 +170,140 @@ "show_call_graph(sub_symb_g)\n", "show_counts_sigma(sub_symb_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "12650fb1", + "metadata": { + "cq.autogen": "SubtractFrom.bloq_doc.md" + }, + "source": [ + "## `SubtractFrom`\n", + "A version of `Subtract` that subtracts the first register from the second in place.\n", + "\n", + "Implements $U|a\n", + "angle|b\n", + "angle\n", + "ightarrow |a\n", + "angle|b - a\n", + "angle$, essentially equivalent to\n", + "the statement `b -= a`.\n", + "\n", + "#### Parameters\n", + " - `dtype`: Quantum datatype used to represent the integers a, b, and b - a. \n", + "\n", + "#### Registers\n", + " - `a`: A dtype.bitsize-sized input register (register a above).\n", + " - `b`: A dtype.bitsize-sized input/output register (register b above).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "746c25eb", + "metadata": { + "cq.autogen": "SubtractFrom.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import SubtractFrom" + ] + }, + { + "cell_type": "markdown", + "id": "75ea545f", + "metadata": { + "cq.autogen": "SubtractFrom.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7ee57a8", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_symb" + }, + "outputs": [], + "source": [ + "n = sympy.Symbol('n')\n", + "sub_from_symb = SubtractFrom(QInt(bitsize=n))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8123a738", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_small" + }, + "outputs": [], + "source": [ + "sub_from_small = SubtractFrom(QInt(bitsize=4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e469b17a", + "metadata": { + "cq.autogen": "SubtractFrom.sub_from_large" + }, + "outputs": [], + "source": [ + "sub_from_large = SubtractFrom(QInt(bitsize=64))" + ] + }, + { + "cell_type": "markdown", + "id": "a32af1e3", + "metadata": { + "cq.autogen": "SubtractFrom.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0fd2dbd7", + "metadata": { + "cq.autogen": "SubtractFrom.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([sub_from_symb, sub_from_small, sub_from_large],\n", + " ['`sub_from_symb`', '`sub_from_small`', '`sub_from_large`'])" + ] + }, + { + "cell_type": "markdown", + "id": "47e50576", + "metadata": { + "cq.autogen": "SubtractFrom.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4564d561", + "metadata": { + "cq.autogen": "SubtractFrom.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "sub_from_symb_g, sub_from_symb_sigma = sub_from_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(sub_from_symb_g)\n", + "show_counts_sigma(sub_from_symb_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 88a392154..b1358855a 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -69,7 +69,7 @@ def b_dtype_default(self): @a_dtype.validator def _a_dtype_validate(self, field, val): if not isinstance(val, (QInt, QUInt, QMontgomeryUInt)): - raise ValueError("Only QInt, QUInt and QMontgomerUInt types are supported.") + raise ValueError("Only QInt, QUInt and QMontgomeryUInt types are supported.") if isinstance(val.num_qubits, sympy.Expr): return if val.bitsize > self.b_dtype.bitsize: @@ -78,13 +78,13 @@ def _a_dtype_validate(self, field, val): @b_dtype.validator def _b_dtype_validate(self, field, val): if not isinstance(val, (QInt, QUInt, QMontgomeryUInt)): - raise ValueError("Only QInt, QUInt and QMontgomerUInt types are supported.") + raise ValueError("Only QInt, QUInt and QMontgomeryUInt types are supported.") @property def dtype(self): if self.a_dtype != self.b_dtype: raise ValueError( - "Add.dtype is only supported when both operands have the same dtype: " + "Subtract.dtype is only supported when both operands have the same dtype: " f"{self.a_dtype=}, {self.b_dtype=}" ) return self.a_dtype @@ -168,3 +168,84 @@ def _sub_diff_size_regs() -> Subtract: _SUB_DOC = BloqDocSpec( bloq_cls=Subtract, examples=[_sub_symb, _sub_small, _sub_large, _sub_diff_size_regs] ) + + +@frozen +class SubtractFrom(Bloq): + """A version of `Subtract` that subtracts the first register from the second in place. + + Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|b - a\rangle$, essentially equivalent to + the statement `b -= a`. + + Args: + dtype: Quantum datatype used to represent the integers a, b, and b - a. + + Registers: + a: A dtype.bitsize-sized input register (register a above). + b: A dtype.bitsize-sized input/output register (register b above). + """ + + dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() + + @dtype.validator + def _dtype_validate(self, field, val): + if not isinstance(val, (QInt, QUInt, QMontgomeryUInt)): + raise ValueError("Only QInt, QUInt and QMontgomeryUInt types are supported.") + + @property + def signature(self): + return Signature([Register("a", self.dtype), Register("b", self.dtype)]) + + def on_classical_vals( + self, a: 'ClassicalValT', b: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + unsigned = isinstance(self.dtype, (QUInt, QMontgomeryUInt)) + bitsize = self.dtype.bitsize + N = 2**bitsize if unsigned else 2 ** (bitsize - 1) + return {'a': a, 'b': int(math.fmod(b - a, N))} + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + from qualtran.drawing import directional_text_box + + if reg is None: + return Text('') + if reg.name == 'a': + return directional_text_box('a', side=reg.side) + elif reg.name == 'b': + return directional_text_box('b - a', side=reg.side) + else: + raise ValueError() + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + return {(Negate(self.dtype), 1), (Subtract(self.dtype, self.dtype), 1)} + + def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: + a, b = bb.add_t(Subtract(self.dtype, self.dtype), a=a, b=b) # a, a - b + b = bb.add(Negate(self.dtype), x=b) # a, b - a + return {'a': a, 'b': b} + + +@bloq_example +def _sub_from_symb() -> SubtractFrom: + n = sympy.Symbol('n') + sub_from_symb = SubtractFrom(QInt(bitsize=n)) + return sub_from_symb + + +@bloq_example +def _sub_from_small() -> SubtractFrom: + sub_from_small = SubtractFrom(QInt(bitsize=4)) + return sub_from_small + + +@bloq_example +def _sub_from_large() -> SubtractFrom: + sub_from_large = SubtractFrom(QInt(bitsize=64)) + return sub_from_large + + +_SUB_FROM_DOC = BloqDocSpec( + bloq_cls=SubtractFrom, examples=[_sub_from_symb, _sub_from_small, _sub_from_large] +) diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index e1da18665..b6c2575bc 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -17,10 +17,36 @@ import qualtran.testing as qlt_testing from qualtran import QInt, QUInt -from qualtran.bloqs.arithmetic import Subtract +from qualtran.bloqs.arithmetic.subtraction import ( + _sub_diff_size_regs, + _sub_from_large, + _sub_from_small, + _sub_from_symb, + _sub_large, + _sub_small, + _sub_symb, + Subtract, + SubtractFrom, +) from qualtran.resource_counting.generalizers import ignore_split_join +def test_sub_symb(bloq_autotester): + bloq_autotester(_sub_symb) + + +def test_sub_small(bloq_autotester): + bloq_autotester(_sub_small) + + +def test_sub_large(bloq_autotester): + bloq_autotester(_sub_large) + + +def test_sub_diff_size_regs(bloq_autotester): + bloq_autotester(_sub_diff_size_regs) + + def test_subtract_bloq_decomposition(): gate = Subtract(QInt(3), QInt(5)) qlt_testing.assert_valid_bloq_decomposition(gate) @@ -41,7 +67,36 @@ def test_subtract_bloq_validation(): assert Subtract(QUInt(3)).dtype == QUInt(3) -def test_subtract_bloq_consitant_counts(): +def test_subtract_bloq_consistent_counts(): qlt_testing.assert_equivalent_bloq_counts( Subtract(QInt(3), QInt(4)), generalizer=ignore_split_join ) + + +def test_sub_from_symb(bloq_autotester): + bloq_autotester(_sub_from_symb) + + +def test_sub_from_small(bloq_autotester): + bloq_autotester(_sub_from_small) + + +def test_sub_from_large(bloq_autotester): + bloq_autotester(_sub_from_large) + + +def test_subtract_from_bloq_decomposition(): + gate = SubtractFrom(QInt(4)) + qlt_testing.assert_valid_bloq_decomposition(gate) + + want = np.zeros((256, 256)) + for a_b in range(256): + a, b = a_b >> 4, a_b & 15 + c = (b - a) % 16 + want[(a << 4) | c][a_b] = 1 + got = gate.tensor_contract() + np.testing.assert_allclose(got, want) + + +def test_subtract_from_bloq_consistent_counts(): + qlt_testing.assert_equivalent_bloq_counts(SubtractFrom(QInt(3)), generalizer=ignore_split_join) diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 990250325..839689928 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -23,6 +23,7 @@ import qualtran.bloqs.arithmetic.negate import qualtran.bloqs.arithmetic.permutation import qualtran.bloqs.arithmetic.sorting +import qualtran.bloqs.arithmetic.subtraction import qualtran.bloqs.basic_gates.cnot import qualtran.bloqs.basic_gates.hadamard import qualtran.bloqs.basic_gates.identity @@ -177,6 +178,7 @@ "qualtran.bloqs.arithmetic.sorting.Comparator": qualtran.bloqs.arithmetic.sorting.Comparator, "qualtran.bloqs.arithmetic.sorting.ParallelComparators": qualtran.bloqs.arithmetic.sorting.ParallelComparators, "qualtran.bloqs.arithmetic.subtraction.Subtract": qualtran.bloqs.arithmetic.subtraction.Subtract, + "qualtran.bloqs.arithmetic.subtraction.SubtractFrom": qualtran.bloqs.arithmetic.subtraction.SubtractFrom, "qualtran.bloqs.basic_gates.cnot.CNOT": qualtran.bloqs.basic_gates.cnot.CNOT, "qualtran.bloqs.basic_gates.identity.Identity": qualtran.bloqs.basic_gates.identity.Identity, "qualtran.bloqs.basic_gates.global_phase.GlobalPhase": qualtran.bloqs.basic_gates.global_phase.GlobalPhase, From bb83da36950e24068f70409e4bf6bed7be0da276 Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Fri, 19 Jul 2024 11:27:14 -0700 Subject: [PATCH 4/9] Switch `SubtractFrom` to use `BitwiseNot` (#1164) * Switch `SubtractFrom` to use `BitwiseNot` * Update notebook --- qualtran/bloqs/arithmetic/multiplication.ipynb | 3 +-- qualtran/bloqs/arithmetic/subtraction.py | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/arithmetic/multiplication.ipynb b/qualtran/bloqs/arithmetic/multiplication.ipynb index c5beeeda8..c7326b061 100644 --- a/qualtran/bloqs/arithmetic/multiplication.ipynb +++ b/qualtran/bloqs/arithmetic/multiplication.ipynb @@ -813,8 +813,7 @@ " - `a`: `bitsize`-sized input register.\n", " - `result`: `bitsize`-sized output register. \n", " - `References`: \n", - " - `[Quantum Algorithms and Circuits for Scientific Computing](`: \n", - " - `https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" + " - `[Quantum Algorithms and Circuits for Scientific Computing](https`: //arxiv.org/pdf/1511.08253). Section 2.1.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index b1358855a..7091ef0ea 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -31,6 +31,7 @@ SoquetT, ) from qualtran.bloqs.arithmetic.addition import Add +from qualtran.bloqs.arithmetic.bitwise import BitwiseNot from qualtran.bloqs.arithmetic.negate import Negate from qualtran.drawing import Text @@ -219,11 +220,12 @@ def wire_symbol( raise ValueError() def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return {(Negate(self.dtype), 1), (Subtract(self.dtype, self.dtype), 1)} + return {(BitwiseNot(self.dtype), 2), (Add(self.dtype, self.dtype), 1)} def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: - a, b = bb.add_t(Subtract(self.dtype, self.dtype), a=a, b=b) # a, a - b - b = bb.add(Negate(self.dtype), x=b) # a, b - a + b = bb.add(BitwiseNot(self.dtype), x=b) # a, -1 - b + a, b = bb.add_t(Add(self.dtype, self.dtype), a=a, b=b) # a, a - 1 - b + b = bb.add(BitwiseNot(self.dtype), x=b) # a, -1 - (a - 1 - b) = a, -a + b return {'a': a, 'b': b} From 0968762ab159f96546c2a923945246ce101bb5ea Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Fri, 19 Jul 2024 14:06:55 -0700 Subject: [PATCH 5/9] Create `SparseMatrix` block encoding (#1143) * Create `SparseMatrix` block encoding * Switch to abstract methods * Update notebook * Address feedback * Switch to BoundedQUInt * Address feedback --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 1 + qualtran/bloqs/block_encoding/__init__.py | 1 + .../bloqs/block_encoding/block_encoding.ipynb | 132 +++++++++ .../bloqs/block_encoding/sparse_matrix.py | 276 ++++++++++++++++++ .../block_encoding/sparse_matrix_test.py | 56 ++++ qualtran/conftest.py | 1 + qualtran/serialization/resolver_dict.py | 4 +- 7 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 qualtran/bloqs/block_encoding/sparse_matrix.py create mode 100644 qualtran/bloqs/block_encoding/sparse_matrix_test.py diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index a6cb46a7a..c944d2117 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -628,6 +628,7 @@ qualtran.bloqs.block_encoding.product._PRODUCT_DOC, qualtran.bloqs.block_encoding.linear_combination._LINEAR_COMBINATION_DOC, qualtran.bloqs.block_encoding.phase._PHASE_DOC, + qualtran.bloqs.block_encoding.sparse_matrix._SPARSE_MATRIX_DOC, ], directory=f'{SOURCE_DIR}/bloqs/block_encoding/', ), diff --git a/qualtran/bloqs/block_encoding/__init__.py b/qualtran/bloqs/block_encoding/__init__.py index 0418f6c41..961da4b35 100644 --- a/qualtran/bloqs/block_encoding/__init__.py +++ b/qualtran/bloqs/block_encoding/__init__.py @@ -24,5 +24,6 @@ from qualtran.bloqs.block_encoding.linear_combination import LinearCombination from qualtran.bloqs.block_encoding.phase import Phase from qualtran.bloqs.block_encoding.product import Product +from qualtran.bloqs.block_encoding.sparse_matrix import SparseMatrix from qualtran.bloqs.block_encoding.tensor_product import TensorProduct from qualtran.bloqs.block_encoding.unitary import Unitary diff --git a/qualtran/bloqs/block_encoding/block_encoding.ipynb b/qualtran/bloqs/block_encoding/block_encoding.ipynb index 8d2a01b2e..931eff88e 100644 --- a/qualtran/bloqs/block_encoding/block_encoding.ipynb +++ b/qualtran/bloqs/block_encoding/block_encoding.ipynb @@ -1304,6 +1304,138 @@ "show_call_graph(linear_combination_block_encoding_g)\n", "show_counts_sigma(linear_combination_block_encoding_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "e36b6098", + "metadata": { + "cq.autogen": "SparseMatrix.bloq_doc.md" + }, + "source": [ + "## `SparseMatrix`\n", + "Block encoding of a sparse-access matrix.\n", + "\n", + "Given row, column, and entry oracles $O_r$, $O_c$, and $O_A$ for an $s$-sparse matrix\n", + "$A \\in \\mathbb{C}^{2^n \\times 2^n}$, i.e. one where each row / column has exactly $s$ non-zero\n", + "entries, computes a $(s, n+1, \\epsilon)$-block encoding of $A$ as follows:\n", + "```\n", + " ┌────┐ ┌────┐\n", + " |0> ─┤ ├─ |0> ─────────────┤ ├───────────────\n", + " │ │ ┌──┐ │ │ ┌──┐\n", + " │ U │ = │ n│ ┌────┐ │ O │ ┌────┐ │ n│\n", + "|0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ ├─┤ A ├─X─┤ ├─┤H ├─\n", + " │ │ └──┘ │ O │ │ │ │ │ O* │ └──┘\n", + "|Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X─┤ r ├──────\n", + " └────┘ └────┘ └────┘ └────┘\n", + "```\n", + "\n", + "To encode a matrix of irregular dimension, the matrix should first be embedded into one of\n", + "dimension $2^n \\times 2^n$ for suitable $n$.\n", + "To encode a matrix where each row / column has at most $s$ non-zero entries, some zeroes should\n", + "be treated as if they were non-zero so that each row / column has exactly $s$ non-zero entries.\n", + "\n", + "#### Parameters\n", + " - `row_oracle`: The row oracle $O_r$. See `RowColumnOracle` for definition.\n", + " - `col_oracle`: The column oracle $O_c$. See `RowColumnOracle` for definition.\n", + " - `entry_oracle`: The entry oracle $O_A$. See `EntryOracle` for definition.\n", + " - `eps`: The precision of the block encoding. \n", + "\n", + "#### Registers\n", + " - `system`: The system register.\n", + " - `ancilla`: The ancilla register.\n", + " - `resource`: The resource register (present only if bitsize > 0). \n", + "\n", + "#### References\n", + " - [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87710c02", + "metadata": { + "cq.autogen": "SparseMatrix.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding import SparseMatrix" + ] + }, + { + "cell_type": "markdown", + "id": "8b9bd4f0", + "metadata": { + "cq.autogen": "SparseMatrix.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ad71b09", + "metadata": { + "cq.autogen": "SparseMatrix.sparse_matrix_block_encoding" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding.sparse_matrix import FullRowColumnOracle, UniformEntryOracle\n", + "\n", + "row_oracle = FullRowColumnOracle(2)\n", + "col_oracle = FullRowColumnOracle(2)\n", + "entry_oracle = UniformEntryOracle(system_bitsize=2, entry=0.3)\n", + "sparse_matrix_block_encoding = SparseMatrix(row_oracle, col_oracle, entry_oracle, eps=0)" + ] + }, + { + "cell_type": "markdown", + "id": "ca16f4df", + "metadata": { + "cq.autogen": "SparseMatrix.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c5da6eb", + "metadata": { + "cq.autogen": "SparseMatrix.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([sparse_matrix_block_encoding],\n", + " ['`sparse_matrix_block_encoding`'])" + ] + }, + { + "cell_type": "markdown", + "id": "847bacb4", + "metadata": { + "cq.autogen": "SparseMatrix.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42070c26", + "metadata": { + "cq.autogen": "SparseMatrix.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "sparse_matrix_block_encoding_g, sparse_matrix_block_encoding_sigma = sparse_matrix_block_encoding.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(sparse_matrix_block_encoding_g)\n", + "show_counts_sigma(sparse_matrix_block_encoding_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/block_encoding/sparse_matrix.py b/qualtran/bloqs/block_encoding/sparse_matrix.py new file mode 100644 index 000000000..7f90ffc3b --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix.py @@ -0,0 +1,276 @@ +# 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 abc +from functools import cached_property +from typing import cast, Dict, Tuple + +import numpy as np +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + BoundedQUInt, + QAny, + QBit, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import Rx, Swap +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.block_encoding.lcu_select_and_prepare import PrepareOracle +from qualtran.bloqs.state_preparation.prepare_uniform_superposition import ( + PrepareUniformSuperposition, +) +from qualtran.symbolics import SymbolicFloat, SymbolicInt + + +@frozen +class RowColumnOracle(Bloq, abc.ABC): + r"""Oracle specifying the non-zero rows or columns of a sparse-access matrix. + + In the reference, this is the interface of + $O_r : \ket{\ell}\ket{i} \mapsto \ket{r(i, \ell)}\ket{i}$, and of + $O_c : \ket{\ell}\ket{j} \mapsto \ket{c(j, \ell)}\ket{j}$. + Here, $r(i, \ell)$ and $c(j, \ell)$ give the $\ell$-th nonzero entry in the $i$-th row + and $j$-th column of the matrix, respectively. + + Registers: + l: As input, index specifying the `l`-th non-zero entry to find in row / column `i`. + As output, position of the `l`-th non-zero entry in row / column `i`. + i: The row / column index. + + References: + [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5. + """ + + @property + @abc.abstractmethod + def system_bitsize(self) -> SymbolicInt: + """The number of bits used to represent an index.""" + + @property + @abc.abstractmethod + def num_nonzero(self) -> SymbolicInt: + """The number of nonzero entries in each row or column.""" + + def __attrs_post_init__(self): + if self.num_nonzero > 2**self.system_bitsize: + raise ValueError("Cannot have more than 2 ** system_bitsize non-zero elements") + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + l=BoundedQUInt(self.system_bitsize, self.num_nonzero), i=QUInt(self.system_bitsize) + ) + + +@frozen +class EntryOracle(Bloq, abc.ABC): + r"""Oracle specifying the entries of a sparse-access matrix. + + In the reference, this is the interface of + $$O_A : \ket{0}\ket{i}\ket{j} \mapsto (A_{ij}\ket{0} + \sqrt{1 - |A_{ij}|^2}\ket{i}\ket{j}).$$ + + Registers: + q: The flag qubit that is rotated proportionally to the value of the entry. + i: The row index. + j: The column index. + + References: + [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5. + """ + + @property + @abc.abstractmethod + def system_bitsize(self) -> SymbolicInt: + """The number of bits used to represent an index.""" + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + q=QBit(), i=QAny(self.system_bitsize), j=QAny(self.system_bitsize) + ) + + +@frozen +class SparseMatrix(BlockEncoding): + r"""Block encoding of a sparse-access matrix. + + Given row, column, and entry oracles $O_r$, $O_c$, and $O_A$ for an $s$-sparse matrix + $A \in \mathbb{C}^{2^n \times 2^n}$, i.e. one where each row / column has exactly $s$ non-zero + entries, computes a $(s, n+1, \epsilon)$-block encoding of $A$ as follows: + ``` + ┌────┐ ┌────┐ + |0> ─┤ ├─ |0> ─────────────┤ ├─────────────── + │ │ ┌──┐ │ │ ┌──┐ + │ U │ = │ n│ ┌────┐ │ O │ ┌────┐ │ n│ + |0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ ├─┤ A ├─X─┤ ├─┤H ├─ + │ │ └──┘ │ O │ │ │ │ │ O* │ └──┘ + |Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X─┤ r ├────── + └────┘ └────┘ └────┘ └────┘ + ``` + + To encode a matrix of irregular dimension, the matrix should first be embedded into one of + dimension $2^n \times 2^n$ for suitable $n$. + To encode a matrix where each row / column has at most $s$ non-zero entries, some zeroes should + be treated as if they were non-zero so that each row / column has exactly $s$ non-zero entries. + + Args: + row_oracle: The row oracle $O_r$. See `RowColumnOracle` for definition. + col_oracle: The column oracle $O_c$. See `RowColumnOracle` for definition. + entry_oracle: The entry oracle $O_A$. See `EntryOracle` for definition. + eps: The precision of the block encoding. + + Registers: + system: The system register. + ancilla: The ancilla register. + resource: The resource register (present only if bitsize > 0). + + References: + [Lecture Notes on Quantum Algorithms for Scientific Computation](https://arxiv.org/abs/2201.08309). Lin Lin (2022). Ch. 6.5. + """ + + row_oracle: RowColumnOracle + col_oracle: RowColumnOracle + entry_oracle: EntryOracle + eps: SymbolicFloat + + def __attrs_post_init__(self): + if ( + self.row_oracle.system_bitsize != self.col_oracle.system_bitsize + or self.row_oracle.system_bitsize != self.entry_oracle.system_bitsize + ): + raise ValueError("Row, column, and entry oracles must have same bitsize") + if self.row_oracle.num_nonzero != self.col_oracle.num_nonzero: + raise ValueError("Unequal row and column sparsities are not supported") + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + system=QAny(self.system_bitsize), + ancilla=QAny(self.ancilla_bitsize), + resource=QAny(self.resource_bitsize), # if resource_bitsize is 0, not present + ) + + @cached_property + def system_bitsize(self) -> SymbolicInt: + return self.entry_oracle.system_bitsize + + def pretty_name(self) -> str: + return "B[SparseMatrix]" + + @cached_property + def alpha(self) -> SymbolicFloat: + return self.row_oracle.num_nonzero + + @cached_property + def ancilla_bitsize(self) -> SymbolicInt: + return self.system_bitsize + 1 + + @cached_property + def resource_bitsize(self) -> SymbolicInt: + return 0 + + @cached_property + def epsilon(self) -> SymbolicFloat: + return self.eps + + @property + def target_registers(self) -> Tuple[Register, ...]: + return (self.signature.get_right("system"),) + + @property + def junk_registers(self) -> Tuple[Register, ...]: + return (self.signature.get_right("resource"),) + + @property + def selection_registers(self) -> Tuple[Register, ...]: + return (self.signature.get_right("ancilla"),) + + @property + def signal_state(self) -> PrepareOracle: + # This method will be implemented in the future after PrepareOracle + # is updated for the BlockEncoding interface. + # Github issue: https://github.com/quantumlib/Qualtran/issues/1104 + raise NotImplementedError + + def build_composite_bloq( + self, bb: BloqBuilder, system: SoquetT, ancilla: SoquetT + ) -> Dict[str, SoquetT]: + ancilla_bits = bb.split(cast(Soquet, ancilla)) + q, l = ancilla_bits[0], bb.join(ancilla_bits[1:]) + + diffusion = PrepareUniformSuperposition(n=2**self.system_bitsize) + l = bb.add(diffusion, target=l) + l, system = bb.add_t(self.col_oracle, l=cast(Soquet, l), i=system) + q, l, system = bb.add_t(self.entry_oracle, q=q, i=l, j=system) + l, system = bb.add_t(Swap(self.system_bitsize), x=l, y=system) + l, system = bb.add_t(self.row_oracle.adjoint(), l=l, i=system) + l = bb.add(diffusion.adjoint(), target=l) + + return {"system": system, "ancilla": bb.join(np.concatenate([[q], bb.split(l)]))} + + +@frozen +class FullRowColumnOracle(RowColumnOracle): + """Oracle specifying the non-zero rows or columns of a matrix with full entries.""" + + system_bitsize: SymbolicInt + + @cached_property + def num_nonzero(self) -> SymbolicInt: + return 2**self.system_bitsize + + def build_composite_bloq(self, bb: BloqBuilder, **soqs: SoquetT) -> Dict[str, SoquetT]: + # the l-th non-zero entry is at position l, so do nothing + return soqs + + +@frozen +class UniformEntryOracle(EntryOracle): + """Oracle specifying the entries of a matrix with uniform entries.""" + + system_bitsize: SymbolicInt + entry: float + + def build_composite_bloq( + self, bb: BloqBuilder, q: Soquet, **soqs: SoquetT + ) -> Dict[str, SoquetT]: + soqs["q"] = cast(Soquet, bb.add(Rx(2 * np.arccos(self.entry)), q=q)) + return soqs + + +@bloq_example +def _sparse_matrix_block_encoding() -> SparseMatrix: + from qualtran.bloqs.block_encoding.sparse_matrix import FullRowColumnOracle, UniformEntryOracle + + row_oracle = FullRowColumnOracle(2) + col_oracle = FullRowColumnOracle(2) + entry_oracle = UniformEntryOracle(system_bitsize=2, entry=0.3) + sparse_matrix_block_encoding = SparseMatrix(row_oracle, col_oracle, entry_oracle, eps=0) + return sparse_matrix_block_encoding + + +_SPARSE_MATRIX_DOC = BloqDocSpec( + bloq_cls=SparseMatrix, + import_line="from qualtran.bloqs.block_encoding import SparseMatrix", + examples=[_sparse_matrix_block_encoding], +) diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_test.py b/qualtran/bloqs/block_encoding/sparse_matrix_test.py new file mode 100644 index 000000000..3e74196f5 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_test.py @@ -0,0 +1,56 @@ +# 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 typing import cast + +import numpy as np + +from qualtran import BloqBuilder, QAny, Register, Signature, Soquet +from qualtran.bloqs.basic_gates import IntEffect, IntState +from qualtran.bloqs.block_encoding.sparse_matrix import _sparse_matrix_block_encoding + + +def test_sparse_matrix(bloq_autotester): + bloq_autotester(_sparse_matrix_block_encoding) + + +def test_sparse_matrix_signature(): + bloq = _sparse_matrix_block_encoding() + assert bloq.signature == Signature( + [Register(name="system", dtype=QAny(2)), Register(name="ancilla", dtype=QAny(3))] + ) + + +def test_sparse_matrix_params(): + bloq = _sparse_matrix_block_encoding() + assert bloq.system_bitsize == 2 + assert bloq.alpha == 4 + assert bloq.epsilon == 0 + assert bloq.ancilla_bitsize == 2 + 1 + assert bloq.resource_bitsize == 0 + + +def test_sparse_matrix_tensors(): + bloq = _sparse_matrix_block_encoding() + alpha = bloq.alpha + bb = BloqBuilder() + system = bb.add_register("system", 2) + ancilla = cast(Soquet, bb.add(IntState(0, 3))) + system, ancilla = bb.add_t(bloq, system=system, ancilla=ancilla) + bb.add(IntEffect(0, 3), val=ancilla) + bloq = bb.finalize(system=system) + + from_gate = np.full((4, 4), 0.3) + from_tensors = bloq.tensor_contract() * alpha + np.testing.assert_allclose(from_gate, from_tensors) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index f631b83b9..5484d02a8 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -103,6 +103,7 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'apply_lth_bloq', 'linear_combination_block_encoding', 'phase_block_encoding', + 'sparse_matrix_block_encoding', 'sparse_state_prep_alias_symb', # cannot serialize Shaped 'sparse_permutation', 'permutation_cycle_symb', diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 839689928..39782f0bb 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -43,6 +43,7 @@ import qualtran.bloqs.block_encoding.linear_combination import qualtran.bloqs.block_encoding.phase import qualtran.bloqs.block_encoding.product +import qualtran.bloqs.block_encoding.sparse_matrix import qualtran.bloqs.block_encoding.tensor_product import qualtran.bloqs.block_encoding.unitary import qualtran.bloqs.bookkeeping @@ -222,7 +223,8 @@ "qualtran.bloqs.block_encoding.tensor_product.TensorProduct": qualtran.bloqs.block_encoding.tensor_product.TensorProduct, "qualtran.bloqs.block_encoding.product.Product": qualtran.bloqs.block_encoding.product.Product, "qualtran.bloqs.block_encoding.linear_combination.LinearCombination": qualtran.bloqs.block_encoding.linear_combination.LinearCombination, - "qualtran.bloqs.block_encoding.phase.phase": qualtran.bloqs.block_encoding.phase.Phase, + "qualtran.bloqs.block_encoding.phase.Phase": qualtran.bloqs.block_encoding.phase.Phase, + "qualtran.bloqs.block_encoding.sparse_matrix.SparseMatrix": qualtran.bloqs.block_encoding.sparse_matrix.SparseMatrix, "qualtran.bloqs.bookkeeping.allocate.Allocate": qualtran.bloqs.bookkeeping.allocate.Allocate, "qualtran.bloqs.bookkeeping.arbitrary_clifford.ArbitraryClifford": qualtran.bloqs.bookkeeping.arbitrary_clifford.ArbitraryClifford, "qualtran.bloqs.bookkeeping.auto_partition.AutoPartition": qualtran.bloqs.bookkeeping.auto_partition.AutoPartition, From 1a0e3abeff15d8989bd571c7e552b203a1955fdf Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 Jul 2024 14:34:25 -0700 Subject: [PATCH 6/9] Optimize gate count of Subtract to n-1 Toffolis (#1057) --- qualtran/bloqs/arithmetic/addition.py | 18 +++- qualtran/bloqs/arithmetic/addition_test.py | 7 ++ qualtran/bloqs/arithmetic/subtraction.ipynb | 74 +++++++++------ qualtran/bloqs/arithmetic/subtraction.py | 93 ++++++++++++++++--- qualtran/bloqs/arithmetic/subtraction_test.py | 82 +++++++++++++--- qualtran/simulation/classical_sim.py | 8 +- qualtran/simulation/classical_sim_test.py | 9 +- 7 files changed, 228 insertions(+), 63 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 882722ec7..47e2e1fd1 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -129,8 +129,19 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: unsigned = isinstance(self.a_dtype, (QUInt, QMontgomeryUInt)) b_bitsize = self.b_dtype.bitsize - N = 2**b_bitsize if unsigned else 2 ** (b_bitsize - 1) - return {'a': a, 'b': int(math.fmod(a + b, N))} + N = 2**b_bitsize + if unsigned: + return {'a': a, 'b': int((a + b) % N)} + + # Addition of signed integers can result in overflow. In most classical programming languages (e.g. C++) + # what happens when an overflow happens is left as an implementation detail for compiler designers. + # However for quantum subtraction the operation should be unitary and that means that the unitary of + # the bloq should be a permutation matrix. + # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards + # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N) + # by the same amount modulu $N$. that is add N/2 before addition and then remove it. + half_n = N >> 1 + return {'a': a, 'b': int(a + b + half_n) % N - half_n} def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: wire_symbols = ["In(x)"] * int(self.a_dtype.bitsize) @@ -188,6 +199,9 @@ def decompose_from_registers( # reverse the order of qubits for big endian-ness. input_bits = quregs['a'][::-1] output_bits = quregs['b'][::-1] + if self.b_dtype.bitsize == 1: + yield CNOT().on(input_bits[0], output_bits[0]) + return ancillas = context.qubit_manager.qalloc(self.b_dtype.bitsize - 1)[::-1] # Start off the addition by anding into the ancilla yield And().on(input_bits[0], output_bits[0], ancillas[0]) diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index 858ea21f2..1337dccc2 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -316,6 +316,13 @@ def test_classical_add_k_unsigned(bitsize, k, x, cvs, ctrls, result): assert bloq_classical[-1] == result +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_classical_add_signed_overflow(bitsize): + bloq = Add(QInt(bitsize)) + mx = 2 ** (bitsize - 1) - 1 + assert bloq.call_classically(a=mx, b=mx) == (mx, -2) + + # TODO: write tests for signed integer addition (subtraction) # https://github.com/quantumlib/Qualtran/issues/606 @pytest.mark.parametrize('bitsize,k,x,cvs,ctrls,result', [(5, 2, 0, (1, 0), (1, 0), 2)]) diff --git a/qualtran/bloqs/arithmetic/subtraction.ipynb b/qualtran/bloqs/arithmetic/subtraction.ipynb index d7e5c66f3..584a0dce4 100644 --- a/qualtran/bloqs/arithmetic/subtraction.ipynb +++ b/qualtran/bloqs/arithmetic/subtraction.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "5e1ac363", + "id": "3f62c768", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3a2de513", + "id": "1e50eeb1", "metadata": { "cq.autogen": "top_imports" }, @@ -30,7 +30,7 @@ }, { "cell_type": "markdown", - "id": "b91ab938", + "id": "9c974476", "metadata": { "cq.autogen": "Subtract.bloq_doc.md" }, @@ -38,9 +38,11 @@ "## `Subtract`\n", "An n-bit subtraction gate.\n", "\n", - "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a-b\\rangle$ using $4n - 4 T$ gates.\n", + "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a-b\\rangle$ using $4n - 4$ T gates.\n", "\n", - "This first negates $b$ (assuming a two's complement representation), and then adds $a$ into it.\n", + "This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. This relation is used in\n", + "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/pdf/2007.07391)\n", + "to turn addition into subtraction conditioned on a qubit.\n", "\n", "#### Parameters\n", " - `a_dtype`: Quantum datatype used to represent the integer a.\n", @@ -48,13 +50,16 @@ "\n", "#### Registers\n", " - `a`: A a_dtype.bitsize-sized input register (register a above).\n", - " - `b`: A b_dtype.bitsize-sized input/output register (register b above).\n" + " - `b`: A b_dtype.bitsize-sized input/output register (register b above). \n", + "\n", + "#### References\n", + " - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391). \n" ] }, { "cell_type": "code", "execution_count": null, - "id": "64dd238f", + "id": "0af034cf", "metadata": { "cq.autogen": "Subtract.bloq_doc.py" }, @@ -65,7 +70,7 @@ }, { "cell_type": "markdown", - "id": "4f77ad26", + "id": "9156f4e7", "metadata": { "cq.autogen": "Subtract.example_instances.md" }, @@ -76,7 +81,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2cd50aa6", + "id": "135553b1", "metadata": { "cq.autogen": "Subtract.sub_symb" }, @@ -89,7 +94,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e2862687", + "id": "84c7207a", "metadata": { "cq.autogen": "Subtract.sub_small" }, @@ -101,7 +106,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8b6584fc", + "id": "e98b3206", "metadata": { "cq.autogen": "Subtract.sub_large" }, @@ -113,7 +118,7 @@ { "cell_type": "code", "execution_count": null, - "id": "27312995", + "id": "e003f5ad", "metadata": { "cq.autogen": "Subtract.sub_diff_size_regs" }, @@ -122,9 +127,22 @@ "sub_diff_size_regs = Subtract(QInt(bitsize=4), QInt(bitsize=16))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4aabfa98", + "metadata": { + "cq.autogen": "Subtract.sub_symp_decomposition" + }, + "outputs": [], + "source": [ + "n = sympy.Symbol('n')\n", + "sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq()" + ] + }, { "cell_type": "markdown", - "id": "82c580af", + "id": "9b580c5b", "metadata": { "cq.autogen": "Subtract.graphical_signature.md" }, @@ -135,20 +153,20 @@ { "cell_type": "code", "execution_count": null, - "id": "4e508f72", + "id": "aa412d69", "metadata": { "cq.autogen": "Subtract.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([sub_symb, sub_small, sub_large, sub_diff_size_regs],\n", - " ['`sub_symb`', '`sub_small`', '`sub_large`', '`sub_diff_size_regs`'])" + "show_bloqs([sub_symb, sub_small, sub_large, sub_diff_size_regs, sub_symp_decomposition],\n", + " ['`sub_symb`', '`sub_small`', '`sub_large`', '`sub_diff_size_regs`', '`sub_symp_decomposition`'])" ] }, { "cell_type": "markdown", - "id": "a282a369", + "id": "6fad45f3", "metadata": { "cq.autogen": "Subtract.call_graph.md" }, @@ -159,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43f1884d", + "id": "6a3d1dbc", "metadata": { "cq.autogen": "Subtract.call_graph.py" }, @@ -173,7 +191,7 @@ }, { "cell_type": "markdown", - "id": "12650fb1", + "id": "c4da55d6", "metadata": { "cq.autogen": "SubtractFrom.bloq_doc.md" }, @@ -200,7 +218,7 @@ { "cell_type": "code", "execution_count": null, - "id": "746c25eb", + "id": "ccc21662", "metadata": { "cq.autogen": "SubtractFrom.bloq_doc.py" }, @@ -211,7 +229,7 @@ }, { "cell_type": "markdown", - "id": "75ea545f", + "id": "b8182c9f", "metadata": { "cq.autogen": "SubtractFrom.example_instances.md" }, @@ -222,7 +240,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e7ee57a8", + "id": "4b56cde9", "metadata": { "cq.autogen": "SubtractFrom.sub_from_symb" }, @@ -235,7 +253,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8123a738", + "id": "cc45ae49", "metadata": { "cq.autogen": "SubtractFrom.sub_from_small" }, @@ -247,7 +265,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e469b17a", + "id": "8e40b0fb", "metadata": { "cq.autogen": "SubtractFrom.sub_from_large" }, @@ -258,7 +276,7 @@ }, { "cell_type": "markdown", - "id": "a32af1e3", + "id": "6d965b01", "metadata": { "cq.autogen": "SubtractFrom.graphical_signature.md" }, @@ -269,7 +287,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0fd2dbd7", + "id": "093d2a0d", "metadata": { "cq.autogen": "SubtractFrom.graphical_signature.py" }, @@ -282,7 +300,7 @@ }, { "cell_type": "markdown", - "id": "47e50576", + "id": "420fd950", "metadata": { "cq.autogen": "SubtractFrom.call_graph.md" }, @@ -293,7 +311,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4564d561", + "id": "7d1b9b0e", "metadata": { "cq.autogen": "SubtractFrom.call_graph.py" }, diff --git a/qualtran/bloqs/arithmetic/subtraction.py b/qualtran/bloqs/arithmetic/subtraction.py index 7091ef0ea..0aada6426 100644 --- a/qualtran/bloqs/arithmetic/subtraction.py +++ b/qualtran/bloqs/arithmetic/subtraction.py @@ -11,9 +11,11 @@ # 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 math from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union +import numpy as np import sympy from attrs import field, frozen @@ -22,6 +24,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + QAny, QInt, QMontgomeryUInt, QUInt, @@ -30,9 +33,12 @@ Soquet, SoquetT, ) +from qualtran._infra.composite_bloq import CompositeBloq from qualtran.bloqs.arithmetic.addition import Add from qualtran.bloqs.arithmetic.bitwise import BitwiseNot -from qualtran.bloqs.arithmetic.negate import Negate +from qualtran.bloqs.basic_gates import OnEach, XGate +from qualtran.bloqs.bookkeeping import Allocate, Cast, Free +from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT from qualtran.drawing import Text if TYPE_CHECKING: @@ -45,9 +51,11 @@ class Subtract(Bloq): r"""An n-bit subtraction gate. - Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4 T$ gates. + Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a-b\rangle$ using $4n - 4$ T gates. - This first negates $b$ (assuming a two's complement representation), and then adds $a$ into it. + This construction uses the relation `a - b = ~(~a + b)` to turn the operation into addition. This relation is used in + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/pdf/2007.07391) + to turn addition into subtraction conditioned on a qubit. Args: a_dtype: Quantum datatype used to represent the integer a. @@ -58,6 +66,9 @@ class Subtract(Bloq): Registers: a: A a_dtype.bitsize-sized input register (register a above). b: A b_dtype.bitsize-sized input/output register (register b above). + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization, page 9](https://arxiv.org/pdf/2007.07391) """ a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() @@ -112,8 +123,18 @@ def on_classical_vals( ) -> Dict[str, 'ClassicalValT']: unsigned = isinstance(self.a_dtype, (QUInt, QMontgomeryUInt)) b_bitsize = self.b_dtype.bitsize - N = 2**b_bitsize if unsigned else 2 ** (b_bitsize - 1) - return {'a': a, 'b': int(math.fmod(a - b, N))} + N = 2**b_bitsize + if unsigned: + return {'a': a, 'b': int((a - b) % N)} + # Subtraction of signed integers can result in overflow. In most classical programming languages (e.g. C++) + # what happens when an overflow happens is left as an implementation detail for compiler designers. + # However for quantum subtraction the operation should be unitary and that means that the unitary of + # the bloq should be a permutation matrix. + # If we hold `a` constant then the valid range of values of `b` [-N/2, N/2) gets shifted forward or backwards + # by `a`. to keep the operation unitary overflowing values wrap around. this is the same as moving the range [0, N) + # by the same amount modulu $N$. that is add N/2 before subtraction and then remove it. + half_n = N >> 1 + return {'a': a, 'b': int((a - b + half_n) % N) - half_n} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -130,14 +151,54 @@ def wire_symbol( raise ValueError() def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return { - (Negate(self.b_dtype), 1), - (Add(self.a_dtype_as_unsigned, self.b_dtype_as_unsigned), 1), - } + delta = self.b_dtype.bitsize - self.a_dtype.bitsize + return ( + { + (OnEach(self.b_dtype.bitsize, XGate()), 3), + (Add(QUInt(self.b_dtype.bitsize), QUInt(self.b_dtype.bitsize)), 1), + } + .union( + [(MultiTargetCNOT(delta), 2)] if delta and isinstance(self.a_dtype, QInt) else [] + ) + .union([(Allocate(QAny(delta)), 1), (Free(QAny(delta)), 1)] if delta else []) + ) def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: - 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 + delta = self.b_dtype.bitsize - self.a_dtype.bitsize + n_bits = self.b_dtype.bitsize + if delta: + if not isinstance(delta, int): + raise ValueError( + 'Symbolic decomposition not supported when a_dtype != b_dtype' + ) # pragma: no cover + a_split = bb.split(a) + prefix = bb.allocate(delta) + if isinstance(self.a_dtype, QInt): + a_split[0], prefix = bb.add( + MultiTargetCNOT(delta), control=a_split[0], targets=prefix + ) + prefix_split = bb.split(prefix) + a_split = np.concatenate([prefix_split, a_split]) + a = bb.join(a_split, QUInt(n_bits)) + else: + a = bb.add(Cast(self.a_dtype, QUInt(n_bits)), reg=a) + b = bb.add(Cast(self.b_dtype, QUInt(n_bits)), reg=b) + a = bb.add(OnEach(n_bits, XGate()), q=a) + a, b = bb.add(Add(QUInt(n_bits)), a=a, b=b) + b = bb.add(OnEach(n_bits, XGate()), q=b) + a = bb.add(OnEach(n_bits, XGate()), q=a) + b = bb.add(Cast(QUInt(n_bits), self.b_dtype), reg=b) + if delta: + a_split = bb.split(a) + prefix = bb.join(a_split[:delta]) + if isinstance(self.a_dtype, QInt): + a_split[delta], prefix = bb.add( + MultiTargetCNOT(delta), control=a_split[delta], targets=prefix + ) + prefix = bb.add(Cast(prefix.reg.dtype, QAny(delta)), reg=prefix) + bb.free(prefix) + a = bb.join(a_split[delta:], QUInt(self.a_dtype.bitsize)) + a = bb.add(Cast(QUInt(self.a_dtype.bitsize), self.a_dtype), reg=a) return {'a': a, 'b': b} @@ -166,8 +227,16 @@ def _sub_diff_size_regs() -> Subtract: return sub_diff_size_regs +@bloq_example +def _sub_symp_decomposition() -> CompositeBloq: + n = sympy.Symbol('n') + sub_symp_decomposition = Subtract(QInt(bitsize=n)).decompose_bloq() + return sub_symp_decomposition + + _SUB_DOC = BloqDocSpec( - bloq_cls=Subtract, examples=[_sub_symb, _sub_small, _sub_large, _sub_diff_size_regs] + bloq_cls=Subtract, + examples=[_sub_symb, _sub_small, _sub_large, _sub_diff_size_regs, _sub_symp_decomposition], ) diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index b6c2575bc..5aa3c488b 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools + import numpy as np import pytest @@ -47,15 +49,45 @@ def test_sub_diff_size_regs(bloq_autotester): bloq_autotester(_sub_diff_size_regs) -def test_subtract_bloq_decomposition(): - gate = Subtract(QInt(3), QInt(5)) +@pytest.mark.parametrize( + ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] +) +def test_subtract_bloq_decomposition_unsigned(a_bits, b_bits): + gate = Subtract(QUInt(a_bits), QUInt(b_bits)) qlt_testing.assert_valid_bloq_decomposition(gate) - want = np.zeros((256, 256)) - for a_b in range(256): - a, b = a_b >> 5, a_b & 31 - c = (a - b) % 32 - want[(a << 5) | c][a_b] = 1 + tot = 1 << (a_bits + b_bits) + want = np.zeros((tot, tot)) + max_b = 1 << b_bits + for a_b in range(tot): + a, b = a_b >> b_bits, a_b & (max_b - 1) + c = (a - b) % max_b + want[(a << b_bits) | c][a_b] = 1 + got = gate.tensor_contract() + np.testing.assert_allclose(got, want) + + +def _to_signed_binary(x: int, bits: int): + if x >= 0: + return x + return (~(-x) + 1) % (2 << bits) + + +@pytest.mark.parametrize( + ['a_bits', 'b_bits'], [(a, b) for a in range(1, 6) for b in range(a, 6) if a + b <= 10] +) +def test_subtract_bloq_decomposition_signed(a_bits, b_bits): + gate = Subtract(QInt(a_bits + 1), QInt(b_bits + 1)) + qlt_testing.assert_valid_bloq_decomposition(gate) + + tot = 1 << (a_bits + b_bits + 2) + want = np.zeros((tot, tot)) + for a in range(-(1 << a_bits), (1 << a_bits)): + for b in range(-(1 << b_bits), (1 << b_bits)): + a_binary = _to_signed_binary(a, a_bits) + b_binary = _to_signed_binary(b, b_bits) + c_binary = _to_signed_binary(a - b, b_bits) + want[(a_binary << b_bits << 1) | c_binary, (a_binary << b_bits << 1) | b_binary] = 1 got = gate.tensor_contract() np.testing.assert_allclose(got, want) @@ -73,6 +105,38 @@ def test_subtract_bloq_consistent_counts(): ) +@pytest.mark.parametrize('n_bits', range(1, 10)) +def test_t_complexity(n_bits): + complexity = Subtract(QUInt(n_bits)).t_complexity() + assert complexity.t == 4 * (n_bits - 1) + assert complexity.rotations == 0 + + +@pytest.mark.parametrize('dtype', [QInt, QUInt]) +def test_against_classical_values(dtype): + subtract = Subtract(dtype(3), dtype(5)) + cbloq = subtract.decompose_bloq() + if dtype is QInt: + R1 = range(-4, 4) + R2 = range(-16, 16) + else: + R1 = range(8) + R2 = range(32) + for (a, b) in itertools.product(R1, R2): + ref = subtract.call_classically(a=a, b=b) + comp = cbloq.call_classically(a=a, b=b) + assert ref == comp + + +@pytest.mark.parametrize('bitsize', range(2, 5)) +def test_classical_add_signed_overflow(bitsize): + bloq = Subtract(QInt(bitsize)) + cbloq = bloq.decompose_bloq() + mn = -(2 ** (bitsize - 1)) + assert bloq.call_classically(a=0, b=mn) == (0, mn) + assert cbloq.call_classically(a=0, b=mn) == (0, mn) + + def test_sub_from_symb(bloq_autotester): bloq_autotester(_sub_from_symb) @@ -96,7 +160,3 @@ def test_subtract_from_bloq_decomposition(): want[(a << 4) | c][a_b] = 1 got = gate.tensor_contract() np.testing.assert_allclose(got, want) - - -def test_subtract_from_bloq_consistent_counts(): - qlt_testing.assert_equivalent_bloq_counts(SubtractFrom(QInt(3)), generalizer=ignore_split_join) diff --git a/qualtran/simulation/classical_sim.py b/qualtran/simulation/classical_sim.py index 2c74108ea..d62e016e3 100644 --- a/qualtran/simulation/classical_sim.py +++ b/qualtran/simulation/classical_sim.py @@ -62,13 +62,7 @@ def ints_to_bits( w: The bit width of the returned bitstrings. """ x = np.atleast_1d(x) - if not np.issubdtype(x.dtype, np.uint): - assert np.all(x >= 0) - assert np.iinfo(x.dtype).bits <= 64 - x = x.astype(np.uint64) - assert w <= np.iinfo(x.dtype).bits - mask = 2 ** np.arange(w - 1, 0 - 1, -1, dtype=x.dtype).reshape((w, 1)) - return (x & mask).astype(bool).astype(np.uint8).T + return np.array([list(map(int, np.binary_repr(v, width=w))) for v in x], dtype=np.uint8) def _get_in_vals( diff --git a/qualtran/simulation/classical_sim_test.py b/qualtran/simulation/classical_sim_test.py index b9b422d06..bd56280b7 100644 --- a/qualtran/simulation/classical_sim_test.py +++ b/qualtran/simulation/classical_sim_test.py @@ -55,6 +55,10 @@ def test_int_to_bits(): bitstrings = ints_to_bits(nums, w=23) assert bitstrings.shape == (100, 23) + nums = rs.randint(-(2**22), 2**22, size=(100,), dtype=np.int64) + bitstrings = ints_to_bits(nums, w=23) + assert bitstrings.shape == (100, 23) + for num, bs in zip(nums, bitstrings): ref_bs = cirq.big_endian_int_to_bits(int(num), bit_count=23) np.testing.assert_array_equal(ref_bs, bs) @@ -63,9 +67,8 @@ def test_int_to_bits(): (bitstring,) = ints_to_bits(2, w=8) assert bitstring.tolist() == [0, 0, 0, 0, 0, 0, 1, 0] - # check bounds - with pytest.raises(AssertionError): - ints_to_bits([4, -2], w=8) + bitstring = ints_to_bits([31, -1], w=6) + assert bitstring.tolist() == [[0, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]] def test_dtype_validation(): From 7eac4ff2c2ca0d5ce0d76df04079b51c55f5efce Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 19 Jul 2024 22:14:48 +0000 Subject: [PATCH 7/9] CHadamard (#1114) * CHadamard * Review feedback * missed an important change! --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 5 +- qualtran/bloqs/basic_gates/__init__.py | 2 +- qualtran/bloqs/basic_gates/hadamard.ipynb | 115 ++++++++++++++++-- qualtran/bloqs/basic_gates/hadamard.py | 107 ++++++++++++++-- qualtran/bloqs/basic_gates/hadamard_test.py | 47 ++++++- .../bloqs/multiplexers/apply_lth_bloq_test.py | 3 +- qualtran/cirq_interop/_cirq_to_bloq.py | 2 + .../t_complexity_protocol_test.py | 3 + qualtran/serialization/resolver_dict.py | 1 + 9 files changed, 262 insertions(+), 23 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index c944d2117..834e6e4b5 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -133,7 +133,10 @@ NotebookSpecV2( title='Hadamard', module=qualtran.bloqs.basic_gates.hadamard, - bloq_specs=[qualtran.bloqs.basic_gates.hadamard._HADAMARD_DOC], + bloq_specs=[ + qualtran.bloqs.basic_gates.hadamard._HADAMARD_DOC, + qualtran.bloqs.basic_gates.hadamard._CHADAMARD_DOC, + ], ), NotebookSpecV2( title='CNOT', diff --git a/qualtran/bloqs/basic_gates/__init__.py b/qualtran/bloqs/basic_gates/__init__.py index 82d8f7837..0f7b9e6fe 100644 --- a/qualtran/bloqs/basic_gates/__init__.py +++ b/qualtran/bloqs/basic_gates/__init__.py @@ -23,7 +23,7 @@ from .cnot import CNOT from .global_phase import GlobalPhase -from .hadamard import Hadamard +from .hadamard import CHadamard, Hadamard from .identity import Identity from .on_each import OnEach from .power import Power diff --git a/qualtran/bloqs/basic_gates/hadamard.ipynb b/qualtran/bloqs/basic_gates/hadamard.ipynb index 93fcce38f..4dc4e7e8e 100644 --- a/qualtran/bloqs/basic_gates/hadamard.ipynb +++ b/qualtran/bloqs/basic_gates/hadamard.ipynb @@ -109,38 +109,131 @@ }, { "cell_type": "markdown", - "id": "6dc303ab", + "id": "fbc791ac", "metadata": { - "cq.autogen": "Hadamard.call_graph.md" + "cq.autogen": "CHadamard.bloq_doc.md" }, "source": [ - "### Call Graph" + "## `CHadamard`\n", + "The controlled Hadamard gate\n", + "\n", + "#### Registers\n", + " - `ctrl`: The control qubit.\n", + " - `q`: The target qubit.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce21798b", + "metadata": { + "cq.autogen": "CHadamard.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import CHadamard" + ] + }, + { + "cell_type": "markdown", + "id": "01eb9903", + "metadata": { + "cq.autogen": "CHadamard.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e79299e", + "metadata": { + "cq.autogen": "CHadamard.chadamard" + }, + "outputs": [], + "source": [ + "chadamard = Hadamard().controlled()\n", + "assert isinstance(chadamard, CHadamard)" + ] + }, + { + "cell_type": "markdown", + "id": "47b363a1", + "metadata": { + "cq.autogen": "CHadamard.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" ] }, { "cell_type": "code", "execution_count": null, - "id": "19f5aeb1", + "id": "01cc88a3", "metadata": { - "cq.autogen": "Hadamard.call_graph.py" + "cq.autogen": "CHadamard.graphical_signature.py" }, "outputs": [], "source": [ - "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "hadamard_g, hadamard_sigma = hadamard.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(hadamard_g)\n", - "show_counts_sigma(hadamard_sigma)" + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([chadamard],\n", + " ['`chadamard`'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf5fe532-b44b-4570-a52c-cd915fb8bf3d", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(chadamard, 'musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "bd22a519-40bd-4275-8fb1-e4981e6ac4d9", + "metadata": {}, + "source": [ + "### Specialty circuits\n", + "\n", + "The `CHadamard` bloq is atomic and cannot be decomposed with `.decompose_bloq()`. An actual implementation on an error-corrected quantum computer will likely be architecture-dependent. A naive circuit for CHadamard can be found using Cirq." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5608b887-c3c8-49db-858a-d06df9005078", + "metadata": {}, + "outputs": [], + "source": [ + "circuit = cirq.Circuit(cirq.decompose_multi_controlled_rotation(\n", + " cirq.unitary(cirq.H),\n", + " controls=[cirq.NamedQubit('ctrl')],\n", + " target=cirq.NamedQubit('q'),\n", + "))\n", + "circuit" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/bloqs/basic_gates/hadamard.py b/qualtran/bloqs/basic_gates/hadamard.py index 02a3bf324..c08c889b2 100644 --- a/qualtran/bloqs/basic_gates/hadamard.py +++ b/qualtran/bloqs/basic_gates/hadamard.py @@ -13,23 +13,27 @@ # limitations under the License. from functools import cached_property -from typing import Dict, List, Optional, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union import numpy as np from attrs import frozen from qualtran import ( + AddControlledT, Bloq, bloq_example, + BloqBuilder, BloqDocSpec, CompositeBloq, ConnectionT, + CtrlSpec, DecomposeTypeError, Register, Signature, + SoquetT, ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity -from qualtran.drawing import Text, TextBox, WireSymbol +from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: import cirq @@ -76,6 +80,23 @@ def my_tensors( ) ] + def get_ctrl_system( + self, ctrl_spec: Optional['CtrlSpec'] = None + ) -> Tuple['Bloq', 'AddControlledT']: + if not (ctrl_spec is None or ctrl_spec == CtrlSpec()): + return super().get_ctrl_system(ctrl_spec=ctrl_spec) + + bloq = CHadamard() + + def _add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + ctrl, q = bb.add(bloq, ctrl=ctrl, target=in_soqs['q']) + return ((ctrl,), (q,)) + + return bloq, _add_ctrled + def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' # type: ignore[type-var] ) -> Tuple['cirq.Operation', Dict[str, 'CirqQuregT']]: # type: ignore[type-var] @@ -102,8 +123,80 @@ def _hadamard() -> Hadamard: return hadamard -_HADAMARD_DOC = BloqDocSpec( - bloq_cls=Hadamard, - import_line='from qualtran.bloqs.basic_gates import Hadamard', - examples=[_hadamard], -) +_HADAMARD_DOC = BloqDocSpec(bloq_cls=Hadamard, examples=[_hadamard], call_graph_example=None) + + +@frozen +class CHadamard(Bloq): + r"""The controlled Hadamard gate + + Registers: + ctrl: The control qubit. + target: The target qubit. + """ + + @cached_property + def signature(self) -> 'Signature': + return Signature.build(ctrl=1, target=1) + + def decompose_bloq(self) -> 'CompositeBloq': + raise DecomposeTypeError(f"{self} is atomic") + + def adjoint(self) -> 'Bloq': + return self + + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + unitary = np.eye(4, dtype=np.complex128).reshape((2, 2, 2, 2)) + # Use these inds orderings to set the block where ctrl=1 to the desired gate. + inds = [ + (outgoing['ctrl'], 0), + (outgoing['target'], 0), + (incoming['ctrl'], 0), + (incoming['target'], 0), + ] + unitary[1, :, 1, :] = _HADAMARD + + return [qtn.Tensor(data=unitary, inds=inds, tags=[str(self)])] + + def as_cirq_op( + self, qubit_manager: 'cirq.QubitManager', ctrl: 'CirqQuregT', target: 'CirqQuregT' + ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: + import cirq + + (ctrl,) = ctrl + (target,) = target + return cirq.H.on(target).controlled_by(ctrl), { + 'ctrl': np.array([ctrl]), + 'target': np.array([target]), + } + + def _t_complexity_(self) -> 'TComplexity': + # This is based on the decomposition provided by `cirq.decompose_multi_controlled_rotation` + # which uses three cirq.MatrixGate's to do a controlled version of any single-qubit gate. + # The first MatrixGate happens to be a clifford, Hadamard operation in this case. + # The other two are considered 'rotations'. + # https://github.com/quantumlib/Qualtran/issues/237 + return TComplexity(rotations=2, clifford=4) + + def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': + if reg is None: + return Text('') + if reg.name == 'ctrl': + return Circle() + if reg.name == 'target': + return TextBox('H') + raise ValueError(f"Unknown register {reg}") + + +@bloq_example +def _chadamard() -> CHadamard: + chadamard = Hadamard().controlled() + assert isinstance(chadamard, CHadamard) + return chadamard + + +_CHADAMARD_DOC = BloqDocSpec(bloq_cls=CHadamard, examples=[_chadamard], call_graph_example=None) diff --git a/qualtran/bloqs/basic_gates/hadamard_test.py b/qualtran/bloqs/basic_gates/hadamard_test.py index 44f43a3f9..4ef7429fa 100644 --- a/qualtran/bloqs/basic_gates/hadamard_test.py +++ b/qualtran/bloqs/basic_gates/hadamard_test.py @@ -16,8 +16,9 @@ import pytest from qualtran import BloqBuilder -from qualtran.bloqs.basic_gates import Hadamard, OneState -from qualtran.bloqs.basic_gates.hadamard import _hadamard +from qualtran.bloqs.basic_gates import Hadamard, OneEffect, OneState +from qualtran.bloqs.basic_gates.hadamard import _hadamard, CHadamard +from qualtran.cirq_interop import cirq_gate_to_bloq def test_to_cirq(): @@ -47,3 +48,45 @@ def test_not_classical(): h = Hadamard() with pytest.raises(NotImplementedError, match=r'.*is not classically simulable\.'): h.call_classically(q=0) + + +def test_chadamard_vs_cirq(): + bloq = Hadamard().controlled() + assert bloq == CHadamard() + + gate = cirq.H.controlled() + np.testing.assert_allclose(cirq.unitary(gate), bloq.tensor_contract()) + + +def test_cirq_interop(): + circuit = CHadamard().as_composite_bloq().to_cirq_circuit() + should_be = cirq.Circuit( + [cirq.Moment(cirq.H(cirq.NamedQubit('target')).controlled_by(cirq.NamedQubit('ctrl')))] + ) + assert circuit == should_be + + (op,) = list(should_be.all_operations()) + assert op.gate is not None + assert cirq_gate_to_bloq(op.gate) == CHadamard() + + +def test_active_chadamard_is_hadamard(): + bb = BloqBuilder() + q = bb.add_register('q', 1) + ctrl_on = bb.add(OneState()) + ctrl_on, q = bb.add(CHadamard(), ctrl=ctrl_on, target=q) + bb.add(OneEffect(), q=ctrl_on) + cbloq = bb.finalize(q=q) + + np.testing.assert_allclose(Hadamard().tensor_contract(), cbloq.tensor_contract()) + + +def test_chadamard_adjoint(): + bb = BloqBuilder() + ctrl = bb.add_register('ctrl', 1) + q = bb.add_register('q', 1) + ctrl, q = bb.add(CHadamard(), ctrl=ctrl, target=q) + ctrl, q = bb.add(CHadamard().adjoint(), ctrl=ctrl, target=q) + cbloq = bb.finalize(ctrl=ctrl, q=q) + + np.testing.assert_allclose(np.eye(4), cbloq.tensor_contract(), atol=1e-12) diff --git a/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py b/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py index ed75f3231..dccc91c82 100644 --- a/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py +++ b/qualtran/bloqs/multiplexers/apply_lth_bloq_test.py @@ -28,6 +28,7 @@ Soquet, ) from qualtran.bloqs.basic_gates import ( + CHadamard, CNOT, Hadamard, Identity, @@ -74,7 +75,7 @@ def test_bloq_has_consistent_decomposition(): def test_call_graph(): _, sigma = _apply_lth_bloq().call_graph(generalizer=ignore_split_join) assert sigma == { - Controlled(Hadamard(), CtrlSpec()): 1, + CHadamard(): 1, Controlled(TGate(), CtrlSpec()): 1, Controlled(ZGate(), CtrlSpec()): 1, CNOT(): 4, diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index e14da41ec..46ee27f62 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -327,6 +327,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: """ from qualtran import Adjoint from qualtran.bloqs.basic_gates import ( + CHadamard, CNOT, CSwap, CZPowGate, @@ -367,6 +368,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: cirq.S: SGate(), cirq.S**-1: SGate().adjoint(), cirq.H: Hadamard(), + cirq.ControlledGate(cirq.H): CHadamard(), cirq.CNOT: CNOT(), cirq.TOFFOLI: Toffoli(), cirq.X: XGate(), diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py index 6ed67ac40..178a19862 100644 --- a/qualtran/cirq_interop/t_complexity_protocol_test.py +++ b/qualtran/cirq_interop/t_complexity_protocol_test.py @@ -19,6 +19,7 @@ from qualtran import Bloq, GateWithRegisters, Signature from qualtran._infra.gate_with_registers import get_named_qubits +from qualtran.bloqs.basic_gates import CHadamard from qualtran.bloqs.mcmt.and_bloq import And from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity from qualtran.cirq_interop.testing import GateHelper @@ -135,6 +136,8 @@ def test_gates(): assert t_complexity(And() ** -1) == TComplexity(clifford=4) assert t_complexity(cirq.FREDKIN) == TComplexity(t=7, clifford=14) + assert t_complexity(cirq.H.controlled()) == TComplexity(clifford=4, rotations=2) + assert t_complexity(CHadamard()) == TComplexity(clifford=4, rotations=2) # Global phase assert t_complexity(cirq.GlobalPhaseGate(1j)) == TComplexity() diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 39782f0bb..cb1fa3214 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -184,6 +184,7 @@ "qualtran.bloqs.basic_gates.identity.Identity": qualtran.bloqs.basic_gates.identity.Identity, "qualtran.bloqs.basic_gates.global_phase.GlobalPhase": qualtran.bloqs.basic_gates.global_phase.GlobalPhase, "qualtran.bloqs.basic_gates.hadamard.Hadamard": qualtran.bloqs.basic_gates.hadamard.Hadamard, + "qualtran.bloqs.basic_gates.hadamard.CHadamard": qualtran.bloqs.basic_gates.hadamard.CHadamard, "qualtran.bloqs.basic_gates.on_each.OnEach": qualtran.bloqs.basic_gates.on_each.OnEach, "qualtran.bloqs.basic_gates.rotation.CZPowGate": qualtran.bloqs.basic_gates.rotation.CZPowGate, "qualtran.bloqs.basic_gates.rotation.Rx": qualtran.bloqs.basic_gates.rotation.Rx, From f997d83501f72f3939545b0a3c96db08cf40c356 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 19 Jul 2024 23:31:35 +0000 Subject: [PATCH 8/9] CYGate (#1115) * CY * Review feedback --------- Co-authored-by: Tanuj Khattar --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 8 + docs/bloqs/index.rst | 1 + qualtran/bloqs/basic_gates/__init__.py | 2 +- qualtran/bloqs/basic_gates/y_gate.ipynb | 221 +++++++++++++++++++ qualtran/bloqs/basic_gates/y_gate.py | 127 ++++++++++- qualtran/bloqs/basic_gates/y_gate_test.py | 59 ++++- qualtran/cirq_interop/_cirq_to_bloq.py | 2 + qualtran/resource_counting/classify_bloqs.py | 25 ++- qualtran/serialization/resolver_dict.py | 1 + 9 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 qualtran/bloqs/basic_gates/y_gate.ipynb diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 834e6e4b5..2da6d974b 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -148,6 +148,14 @@ module=qualtran.bloqs.basic_gates.s_gate, bloq_specs=[qualtran.bloqs.basic_gates.s_gate._S_GATE_DOC], ), + NotebookSpecV2( + title='Y Gate', + module=qualtran.bloqs.basic_gates.y_gate, + bloq_specs=[ + qualtran.bloqs.basic_gates.y_gate._Y_GATE_DOC, + qualtran.bloqs.basic_gates.y_gate._CY_GATE_DOC, + ], + ), NotebookSpecV2( title='And', module=qualtran.bloqs.mcmt.and_bloq, diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 2a5af961c..e68836c22 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -31,6 +31,7 @@ Bloqs Library basic_gates/hadamard.ipynb basic_gates/cnot.ipynb basic_gates/s_gate.ipynb + basic_gates/y_gate.ipynb mcmt/and_bloq.ipynb basic_gates/states_and_effects.ipynb swap_network/swap_network.ipynb diff --git a/qualtran/bloqs/basic_gates/__init__.py b/qualtran/bloqs/basic_gates/__init__.py index 0f7b9e6fe..ab6a40cf4 100644 --- a/qualtran/bloqs/basic_gates/__init__.py +++ b/qualtran/bloqs/basic_gates/__init__.py @@ -34,5 +34,5 @@ from .t_gate import TGate from .toffoli import Toffoli from .x_basis import MinusEffect, MinusState, PlusEffect, PlusState, XGate -from .y_gate import YGate +from .y_gate import CYGate, YGate from .z_basis import IntEffect, IntState, OneEffect, OneState, ZeroEffect, ZeroState, ZGate diff --git a/qualtran/bloqs/basic_gates/y_gate.ipynb b/qualtran/bloqs/basic_gates/y_gate.ipynb new file mode 100644 index 000000000..0a63ac37c --- /dev/null +++ b/qualtran/bloqs/basic_gates/y_gate.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1f6baa88", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Y Gate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4dd57f4", + "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": "39518f37", + "metadata": { + "cq.autogen": "YGate.bloq_doc.md" + }, + "source": [ + "## `YGate`\n", + "The Pauli Y gate.\n", + "\n", + "This causes a bit flip (with a complex phase): Y|0> = 1j|1>, Y|1> = -1j|0>\n", + "\n", + "#### Registers\n", + " - `q`: The qubit.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f9aa8e7", + "metadata": { + "cq.autogen": "YGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import YGate" + ] + }, + { + "cell_type": "markdown", + "id": "f1dae24e", + "metadata": { + "cq.autogen": "YGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c3a5a18", + "metadata": { + "cq.autogen": "YGate.y_gate" + }, + "outputs": [], + "source": [ + "y_gate = YGate()" + ] + }, + { + "cell_type": "markdown", + "id": "0bcd3fda", + "metadata": { + "cq.autogen": "YGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c5d7345", + "metadata": { + "cq.autogen": "YGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([y_gate],\n", + " ['`y_gate`'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a1ba7bf-de51-496d-b637-9db51ed40cce", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(y_gate, 'musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "5f68305f", + "metadata": { + "cq.autogen": "CYGate.bloq_doc.md" + }, + "source": [ + "## `CYGate`\n", + "A controlled Y gate.\n", + "\n", + "#### Registers\n", + " - `ctrl`: The control qubit.\n", + " - `q`: The target qubit.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fa440cd", + "metadata": { + "cq.autogen": "CYGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import CYGate" + ] + }, + { + "cell_type": "markdown", + "id": "6cd4b475", + "metadata": { + "cq.autogen": "CYGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb197f4d", + "metadata": { + "cq.autogen": "CYGate.cy_gate" + }, + "outputs": [], + "source": [ + "cy_gate = YGate().controlled()\n", + "assert isinstance(cy_gate, CYGate)" + ] + }, + { + "cell_type": "markdown", + "id": "61fdd296", + "metadata": { + "cq.autogen": "CYGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad2d7347", + "metadata": { + "cq.autogen": "CYGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([cy_gate],\n", + " ['`cy_gate`'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4242fd0b-17a0-48ad-bae9-ec146c65d810", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(cy_gate, 'musical_score')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/basic_gates/y_gate.py b/qualtran/bloqs/basic_gates/y_gate.py index b3ee7255e..1430e1811 100644 --- a/qualtran/bloqs/basic_gates/y_gate.py +++ b/qualtran/bloqs/basic_gates/y_gate.py @@ -13,13 +13,27 @@ # limitations under the License. from functools import cached_property -from typing import Dict, List, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union import numpy as np from attrs import frozen -from qualtran import Bloq, ConnectionT, Signature +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CompositeBloq, + ConnectionT, + CtrlSpec, + DecomposeTypeError, + Register, + Signature, + SoquetT, +) from qualtran.cirq_interop.t_complexity_protocol import TComplexity +from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: import cirq @@ -35,12 +49,18 @@ class YGate(Bloq): """The Pauli Y gate. This causes a bit flip (with a complex phase): Y|0> = 1j|1>, Y|1> = -1j|0> + + Registers: + q: The qubit. """ @cached_property def signature(self) -> 'Signature': return Signature.build(q=1) + def decompose_bloq(self) -> 'CompositeBloq': + raise DecomposeTypeError(f"{self} is atomic.") + def adjoint(self) -> 'Bloq': return self @@ -55,6 +75,23 @@ def my_tensors( ) ] + def get_ctrl_system( + self, ctrl_spec: Optional['CtrlSpec'] = None + ) -> Tuple['Bloq', 'AddControlledT']: + if not (ctrl_spec is None or ctrl_spec == CtrlSpec()): + return super().get_ctrl_system(ctrl_spec=ctrl_spec) + + bloq = CYGate() + + def _add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + ctrl, q = bb.add(bloq, ctrl=ctrl, target=in_soqs['q']) + return ((ctrl,), (q,)) + + return bloq, _add_ctrled + def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' ) -> Tuple['cirq.Operation', Dict[str, 'CirqQuregT']]: @@ -65,3 +102,89 @@ def as_cirq_op( def _t_complexity_(self) -> 'TComplexity': return TComplexity(clifford=1) + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + return TextBox('Y') + + +@bloq_example +def _y_gate() -> YGate: + y_gate = YGate() + return y_gate + + +_Y_GATE_DOC = BloqDocSpec(bloq_cls=YGate, examples=[_y_gate], call_graph_example=None) + + +@frozen +class CYGate(Bloq): + """A controlled Y gate. + + Registers: + ctrl: The control qubit. + target: The target qubit. + """ + + @cached_property + def signature(self) -> 'Signature': + return Signature.build(ctrl=1, target=1) + + def decompose_bloq(self) -> 'CompositeBloq': + raise DecomposeTypeError(f"{self} is atomic.") + + def adjoint(self) -> 'Bloq': + return self + + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + unitary = np.eye(4, dtype=np.complex128).reshape((2, 2, 2, 2)) + # Use these inds orderings to set the block where ctrl=1 to the desired gate. + inds = [ + (outgoing['ctrl'], 0), + (outgoing['target'], 0), + (incoming['ctrl'], 0), + (incoming['target'], 0), + ] + unitary[1, :, 1, :] = _PAULIY + + return [qtn.Tensor(data=unitary, inds=inds, tags=[str(self)])] + + def as_cirq_op( + self, qubit_manager: 'cirq.QubitManager', ctrl: 'CirqQuregT', target: 'CirqQuregT' + ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: + import cirq + + (ctrl,) = ctrl + (target,) = target + return cirq.Y.on(target).controlled_by(ctrl), { + 'ctrl': np.array([ctrl]), + 'target': np.array([target]), + } + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + if reg.name == 'ctrl': + return Circle() + if reg.name == 'target': + return TextBox('Y') + raise ValueError(f"Unknown register {reg}.") + + +@bloq_example +def _cy_gate() -> CYGate: + cy_gate = YGate().controlled() + assert isinstance(cy_gate, CYGate) + return cy_gate + + +_CY_GATE_DOC = BloqDocSpec(bloq_cls=CYGate, examples=[_cy_gate], call_graph_example=None) diff --git a/qualtran/bloqs/basic_gates/y_gate_test.py b/qualtran/bloqs/basic_gates/y_gate_test.py index 874248c50..e5f6e9def 100644 --- a/qualtran/bloqs/basic_gates/y_gate_test.py +++ b/qualtran/bloqs/basic_gates/y_gate_test.py @@ -15,7 +15,18 @@ import numpy as np from qualtran import BloqBuilder -from qualtran.bloqs.basic_gates import MinusState, PlusState, YGate +from qualtran.bloqs.basic_gates import MinusState, OneEffect, OneState, PlusState, YGate +from qualtran.bloqs.basic_gates.y_gate import _cy_gate, _y_gate, CYGate +from qualtran.cirq_interop import cirq_gate_to_bloq +from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity + + +def test_y_gate(bloq_autotester): + bloq_autotester(_y_gate) + + +def test_cy_gate(bloq_autotester): + bloq_autotester(_cy_gate) def test_to_cirq(): @@ -38,3 +49,49 @@ def test_to_cirq(): vec1 = cbloq.tensor_contract() vec2 = cirq.final_state_vector(circuit) np.testing.assert_allclose(vec1, vec2) + + +def test_cy_vs_cirq(): + bloq = YGate().controlled() + assert bloq == CYGate() + + gate = cirq.Y.controlled() + np.testing.assert_allclose(cirq.unitary(gate), bloq.tensor_contract()) + + +def test_cirq_interop(): + circuit = CYGate().as_composite_bloq().to_cirq_circuit() + should_be = cirq.Circuit( + [cirq.Moment(cirq.Y(cirq.NamedQubit('target')).controlled_by(cirq.NamedQubit('ctrl')))] + ) + assert circuit == should_be + + (op,) = list(should_be.all_operations()) + assert op.gate is not None + assert cirq_gate_to_bloq(op.gate) == CYGate() + + +def test_active_cy_is_y(): + bb = BloqBuilder() + q = bb.add_register('q', 1) + ctrl_on = bb.add(OneState()) + ctrl_on, q = bb.add(CYGate(), ctrl=ctrl_on, target=q) + bb.add(OneEffect(), q=ctrl_on) + cbloq = bb.finalize(q=q) + + np.testing.assert_allclose(YGate().tensor_contract(), cbloq.tensor_contract()) + + +def test_cy_adjoint(): + bb = BloqBuilder() + ctrl = bb.add_register('ctrl', 1) + q = bb.add_register('q', 1) + ctrl, q = bb.add(CYGate(), ctrl=ctrl, target=q) + ctrl, q = bb.add(CYGate().adjoint(), ctrl=ctrl, target=q) + cbloq = bb.finalize(ctrl=ctrl, q=q) + + np.testing.assert_allclose(np.eye(4), cbloq.tensor_contract(), atol=1e-12) + + +def test_cy_t_complexity(): + assert t_complexity(CYGate()) == TComplexity(clifford=1) diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 46ee27f62..2ce5e2a9a 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -330,6 +330,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: CHadamard, CNOT, CSwap, + CYGate, CZPowGate, GlobalPhase, Hadamard, @@ -373,6 +374,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: cirq.TOFFOLI: Toffoli(), cirq.X: XGate(), cirq.Y: YGate(), + cirq.ControlledGate(cirq.Y): CYGate(), cirq.Z: ZGate(), cirq.SWAP: TwoBitSwap(), cirq.CSWAP: CSwap(1), diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index 0b8a51187..4e5107eb1 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -108,7 +108,16 @@ def classify_t_count_by_bloq_type( def bloq_is_clifford(b: Bloq): - from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, TwoBitSwap, XGate, ZGate + from qualtran.bloqs.basic_gates import ( + CNOT, + CYGate, + Hadamard, + SGate, + TwoBitSwap, + XGate, + YGate, + ZGate, + ) from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT @@ -116,7 +125,19 @@ def bloq_is_clifford(b: Bloq): b = b.subbloq if isinstance( - b, (TwoBitSwap, Hadamard, XGate, ZGate, ArbitraryClifford, CNOT, MultiTargetCNOT, SGate) + b, + ( + TwoBitSwap, + Hadamard, + XGate, + ZGate, + YGate, + ArbitraryClifford, + CNOT, + MultiTargetCNOT, + CYGate, + SGate, + ), ): return True diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index cb1fa3214..a4123d35d 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -207,6 +207,7 @@ "qualtran.bloqs.basic_gates.x_basis.PlusState": qualtran.bloqs.basic_gates.x_basis.PlusState, "qualtran.bloqs.basic_gates.x_basis.XGate": qualtran.bloqs.basic_gates.x_basis.XGate, "qualtran.bloqs.basic_gates.y_gate.YGate": qualtran.bloqs.basic_gates.y_gate.YGate, + "qualtran.bloqs.basic_gates.y_gate.CYGate": qualtran.bloqs.basic_gates.y_gate.CYGate, "qualtran.bloqs.basic_gates.z_basis.IntEffect": qualtran.bloqs.basic_gates.z_basis.IntEffect, "qualtran.bloqs.basic_gates.z_basis.IntState": qualtran.bloqs.basic_gates.z_basis.IntState, "qualtran.bloqs.basic_gates.z_basis.OneEffect": qualtran.bloqs.basic_gates.z_basis.OneEffect, From 1f47969fabf143cb2734f6c7d5c2fad093e23c78 Mon Sep 17 00:00:00 2001 From: Charles Yuan Date: Fri, 19 Jul 2024 17:00:09 -0700 Subject: [PATCH 9/9] Add `typing_extensions>=4.10.0` dep (#1159) * Add `typing_extensions>=4.10.0` dep * Update *.env.txt * Re-run with Python 3.10 * Revert changes from a full bump --------- Co-authored-by: Tanuj Khattar Co-authored-by: Matthew Harrigan --- dev_tools/requirements/deps/runtime.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev_tools/requirements/deps/runtime.txt b/dev_tools/requirements/deps/runtime.txt index 2a542b4e7..4c0c8019a 100644 --- a/dev_tools/requirements/deps/runtime.txt +++ b/dev_tools/requirements/deps/runtime.txt @@ -32,4 +32,7 @@ qsharp-widgets # serialization protobuf +# typing +typing_extensions>=4.10.0 + # Note: use `pipreqs` to generate a list of dependencies based on the imports in our files.