From cf5222b270165bd89a827d9c1823a4c375d97c48 Mon Sep 17 00:00:00 2001 From: Fionn Malone Date: Tue, 10 Oct 2023 15:23:50 -0700 Subject: [PATCH] Add more arithmetic gates. (#381) * Add more arithmetic gates. * Fix formatting. * Fix registers formatting. * Use cirq_ft's t_complexity for other comparators bloq_counts / t_complexity. * Fix t_complexity calls. * Address review comments. * Fix unit test. --- dev_tools/autogenerate-bloqs-notebooks.py | 3 + qualtran/bloqs/arithmetic.ipynb | 171 +++++++++++++++++++--- qualtran/bloqs/arithmetic.py | 159 ++++++++++++++++++-- qualtran/bloqs/arithmetic_test.py | 51 ++++++- qualtran/bloqs/sorting_test.py | 4 +- 5 files changed, 349 insertions(+), 39 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks.py b/dev_tools/autogenerate-bloqs-notebooks.py index 2755c558d..50f9f672c 100644 --- a/dev_tools/autogenerate-bloqs-notebooks.py +++ b/dev_tools/autogenerate-bloqs-notebooks.py @@ -112,6 +112,9 @@ BloqNbSpec(qualtran.bloqs.arithmetic_test._make_square), BloqNbSpec(qualtran.bloqs.arithmetic_test._make_sum_of_squares), BloqNbSpec(qualtran.bloqs.arithmetic_test._make_greater_than), + BloqNbSpec(qualtran.bloqs.arithmetic_test._make_greater_than_constant), + BloqNbSpec(qualtran.bloqs.arithmetic_test._make_equals_a_constant), + BloqNbSpec(qualtran.bloqs.arithmetic_test._make_to_contiguous_index), BloqNbSpec(qualtran.bloqs.arithmetic_test._make_scale_int_by_real), BloqNbSpec(qualtran.bloqs.arithmetic_test._make_multiply_two_reals), BloqNbSpec(qualtran.bloqs.arithmetic_test._make_square_real_number), diff --git a/qualtran/bloqs/arithmetic.ipynb b/qualtran/bloqs/arithmetic.ipynb index 0d0dd3f43..9a85193a0 100644 --- a/qualtran/bloqs/arithmetic.ipynb +++ b/qualtran/bloqs/arithmetic.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "40368a5b", + "id": "bd6ce2e3", "metadata": { "cq.autogen": "title_cell" }, @@ -13,7 +13,7 @@ { "cell_type": "code", "execution_count": null, - "id": "410926b9", + "id": "8133a7f0", "metadata": { "cq.autogen": "top_imports" }, @@ -27,7 +27,7 @@ }, { "cell_type": "markdown", - "id": "b92171ba", + "id": "ae9dfdf0", "metadata": { "cq.autogen": "_make_add.md" }, @@ -51,7 +51,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f930d4ff", + "id": "543903f4", "metadata": { "cq.autogen": "_make_add.py" }, @@ -65,7 +65,7 @@ }, { "cell_type": "markdown", - "id": "3b20c805", + "id": "dd75387c", "metadata": { "cq.autogen": "_make_product.md" }, @@ -92,7 +92,7 @@ { "cell_type": "code", "execution_count": null, - "id": "877b583b", + "id": "38185fa9", "metadata": { "cq.autogen": "_make_product.py" }, @@ -106,7 +106,7 @@ }, { "cell_type": "markdown", - "id": "f50caf16", + "id": "4bdb27f5", "metadata": { "cq.autogen": "_make_square.md" }, @@ -130,7 +130,7 @@ { "cell_type": "code", "execution_count": null, - "id": "633eb3ba", + "id": "49618c06", "metadata": { "cq.autogen": "_make_square.py" }, @@ -144,7 +144,7 @@ }, { "cell_type": "markdown", - "id": "18e2cca7", + "id": "e9028de9", "metadata": { "cq.autogen": "_make_sum_of_squares.md" }, @@ -173,7 +173,7 @@ { "cell_type": "code", "execution_count": null, - "id": "95d0069d", + "id": "a5cf7d8b", "metadata": { "cq.autogen": "_make_sum_of_squares.py" }, @@ -187,7 +187,7 @@ }, { "cell_type": "markdown", - "id": "20639a7a", + "id": "190b0a0f", "metadata": { "cq.autogen": "_make_greater_than.md" }, @@ -198,6 +198,11 @@ "Implements $U|a\\rangle|b\\rangle|0\\rangle \\rightarrow\n", "|a\\rangle|b\\rangle|a > b\\rangle$ using $8n T$ gates.\n", "\n", + "The bloq_counts and t_complexity are derived from equivalent cirq_ft gates\n", + "assuming a clean decomposition which should yield identical costs.\n", + "\n", + "See: https://github.com/quantumlib/Qualtran/pull/381 and\n", + "https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html\n", "\n", "#### Parameters\n", " - `bitsize`: Number of bits used to represent the two integers a and b. \n", @@ -205,16 +210,13 @@ "#### Registers\n", " - `a`: n-bit-sized input registers.\n", " - `b`: n-bit-sized input registers.\n", - " - `result`: A single bit output register to store the result of A > B. \n", - "\n", - "#### References\n", - "[Improved techniques for preparing eigenstates of fermionic Hamiltonians](https://www.nature.com/articles/s41534-018-0071-5#additional-information), Comparison Oracle from SI: Appendix 2B (pg 3)\n" + " - `target`: A single bit output register to store the result of A > B.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "af66690c", + "id": "0ea3e1aa", "metadata": { "cq.autogen": "_make_greater_than.py" }, @@ -228,7 +230,130 @@ }, { "cell_type": "markdown", - "id": "1c63d263", + "id": "895d5f80", + "metadata": { + "cq.autogen": "_make_greater_than_constant.md" + }, + "source": [ + "## `GreaterThanConstant`\n", + "Implements $U_a|x\\rangle = U_a|x\\rangle|z\\rangle = |x\\rangle |z \\land (x > a)\\rangle$\n", + "\n", + "The bloq_counts and t_complexity are derived from equivalent cirq_ft gates\n", + "assuming a clean decomposition which should yield identical costs.\n", + "\n", + "See: https://github.com/quantumlib/Qualtran/pull/381 and\n", + "https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html\n", + "\n", + "\n", + "#### Parameters\n", + " - `bitsize`: bitsize of x register.\n", + " - `val`: integer to compare x against (a above.) \n", + "\n", + "#### Registers\n", + " - `x`: Register to compare against val.\n", + " - `target`: Register to hold result of comparison.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "066a16fe", + "metadata": { + "cq.autogen": "_make_greater_than_constant.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import GreaterThanConstant\n", + "\n", + "bloq = GreaterThanConstant(bitsize=4, val=13)\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "0158ca13", + "metadata": { + "cq.autogen": "_make_equals_a_constant.md" + }, + "source": [ + "## `EqualsAConstant`\n", + "Implements $U_a|x\\rangle = U_a|x\\rangle|z\\rangle = |x\\rangle |z \\land (x = a)\\rangle$\n", + "\n", + "The bloq_counts and t_complexity are derived from:\n", + "https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html#equality-as-a-special-case\n", + "\n", + "#### Parameters\n", + " - `bitsize`: bitsize of x register.\n", + " - `val`: integer to compare x against (a above.) \n", + "\n", + "#### Registers\n", + " - `x`: Register to compare against val.\n", + " - `target`: Register to hold result of comparison.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa8d3095", + "metadata": { + "cq.autogen": "_make_equals_a_constant.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import EqualsAConstant\n", + "\n", + "bloq = EqualsAConstant(bitsize=4, val=13)\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "ced59882", + "metadata": { + "cq.autogen": "_make_to_contiguous_index.md" + }, + "source": [ + "## `ToContiguousIndex`\n", + "Build a contiguous register s from mu and nu.\n", + "\n", + "$$\n", + " s = \\nu (\\nu + 1) / 2 + \\mu\n", + "$$\n", + "\n", + "Assuming nu is zero indexed (in contrast to the THC paper which assumes 1,\n", + "hence the slightly different formula).\n", + "\n", + "#### Parameters\n", + " - `bitsize`: number of bits for mu and nu registers.\n", + " - `s_bitsize`: Number of bits for contiguous register. \n", + "\n", + "#### Registers\n", + " - `mu`: input register\n", + " - `nu`: input register\n", + " - `s`: output contiguous register \n", + "\n", + "#### References\n", + "(Even more efficient quantum computations of chemistry through tensor hypercontraction)[https://arxiv.org/pdf/2011.03494.pdf] Eq. 29.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0581d0a", + "metadata": { + "cq.autogen": "_make_to_contiguous_index.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import ToContiguousIndex\n", + "\n", + "bloq = ToContiguousIndex(bitsize=4, s_bitsize=8)\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "12a1747d", "metadata": { "cq.autogen": "_make_scale_int_by_real.md" }, @@ -260,7 +385,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4538d32b", + "id": "e82868fe", "metadata": { "cq.autogen": "_make_scale_int_by_real.py" }, @@ -274,7 +399,7 @@ }, { "cell_type": "markdown", - "id": "6247c0bd", + "id": "20c439a4", "metadata": { "cq.autogen": "_make_multiply_two_reals.md" }, @@ -305,7 +430,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5b8be106", + "id": "6d1dffcc", "metadata": { "cq.autogen": "_make_multiply_two_reals.py" }, @@ -319,7 +444,7 @@ }, { "cell_type": "markdown", - "id": "7b0a73bb", + "id": "c1e1cf01", "metadata": { "cq.autogen": "_make_square_real_number.md" }, @@ -350,7 +475,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ec6335d9", + "id": "12b00bbd", "metadata": { "cq.autogen": "_make_square_real_number.py" }, @@ -379,7 +504,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/qualtran/bloqs/arithmetic.py b/qualtran/bloqs/arithmetic.py index 57ac5810a..52053cd27 100644 --- a/qualtran/bloqs/arithmetic.py +++ b/qualtran/bloqs/arithmetic.py @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Set, Tuple, TYPE_CHECKING +from functools import cached_property +from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING, Union +import sympy from attrs import frozen -from cirq_ft import TComplexity +from cirq_ft import t_complexity, TComplexity +from cirq_ft.algos.arithmetic_gates import LessThanEqualGate, LessThanGate from qualtran import Bloq, Register, Side, Signature from qualtran.bloqs.basic_gates import TGate @@ -421,6 +424,11 @@ class GreaterThan(Bloq): Implements $U|a\rangle|b\rangle|0\rangle \rightarrow |a\rangle|b\rangle|a > b\rangle$ using $8n T$ gates. + The bloq_counts and t_complexity are derived from equivalent cirq_ft gates + assuming a clean decomposition which should yield identical costs. + + See: https://github.com/quantumlib/Qualtran/pull/381 and + https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html Args: bitsize: Number of bits used to represent the two integers a and b. @@ -428,27 +436,152 @@ class GreaterThan(Bloq): Registers: a: n-bit-sized input registers. b: n-bit-sized input registers. - result: A single bit output register to store the result of A > B. - - References: - [Improved techniques for preparing eigenstates of fermionic - Hamiltonians](https://www.nature.com/articles/s41534-018-0071-5#additional-information), - Comparison Oracle from SI: Appendix 2B (pg 3) + target: A single bit output register to store the result of A > B. """ bitsize: int @property def signature(self): - return Signature.build(a=self.bitsize, b=self.bitsize, result=1) + return Signature.build(a=self.bitsize, b=self.bitsize, target=1) def pretty_name(self) -> str: return "a gt b" - def t_complexity(self): + def t_complexity(self) -> 'TComplexity': + return t_complexity(LessThanEqualGate(self.bitsize, self.bitsize)) + + def bloq_counts( + self, ssa: Optional['SympySymbolAllocator'] = None + ) -> Set[Tuple[Union[int, sympy.Expr], Bloq]]: # TODO Determine precise clifford count and/or ignore. # See: https://github.com/quantumlib/cirq-qubitization/issues/219 # See: https://github.com/quantumlib/cirq-qubitization/issues/217 - return TComplexity(t=8 * self.bitsize) + t_complexity = self.t_complexity() + return {(t_complexity.t, TGate())} - def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: - return {(8 * self.bitsize, TGate())} + +@frozen +class GreaterThanConstant(Bloq): + r"""Implements $U_a|x\rangle = U_a|x\rangle|z\rangle = |x\rangle |z \land (x > a)\rangle$ + + The bloq_counts and t_complexity are derived from equivalent cirq_ft gates + assuming a clean decomposition which should yield identical costs. + + See: https://github.com/quantumlib/Qualtran/pull/381 and + https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html + + + Args: + bitsize: bitsize of x register. + val: integer to compare x against (a above.) + + Registers: + x: Register to compare against val. + target: Register to hold result of comparison. + """ + + bitsize: int + val: int + + @cached_property + def signature(self) -> Signature: + return Signature.build(x=self.bitsize, target=1) + + def t_complexity(self) -> TComplexity: + return t_complexity(LessThanGate(self.bitsize, val=self.val)) + + def bloq_counts( + self, ssa: Optional['SympySymbolAllocator'] = None + ) -> Set[Tuple[Union[int, sympy.Expr], Bloq]]: + # TODO Determine precise clifford count and/or ignore. + # See: https://github.com/quantumlib/cirq-qubitization/issues/219 + # See: https://github.com/quantumlib/cirq-qubitization/issues/217 + t_complexity = self.t_complexity() + return {(t_complexity.t, TGate())} + + +@frozen +class EqualsAConstant(Bloq): + r"""Implements $U_a|x\rangle = U_a|x\rangle|z\rangle = |x\rangle |z \land (x = a)\rangle$ + + The bloq_counts and t_complexity are derived from: + https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html#equality-as-a-special-case + + Args: + bitsize: bitsize of x register. + val: integer to compare x against (a above.) + + Registers: + x: Register to compare against val. + target: Register to hold result of comparison. + """ + + bitsize: int + val: int + + @cached_property + def signature(self) -> Signature: + return Signature.build(x=self.bitsize, target=1) + + def t_complexity(self) -> 'TComplexity': + return TComplexity(t=4 * (self.bitsize - 1)) + + def bloq_counts( + self, ssa: Optional['SympySymbolAllocator'] = None + ) -> Set[Tuple[Union[int, sympy.Expr], Bloq]]: + # See: https://github.com/quantumlib/cirq-qubitization/issues/219 + # See: https://github.com/quantumlib/cirq-qubitization/issues/217 + return {(4 * (self.bitsize - 1), TGate())} + + +@frozen +class ToContiguousIndex(Bloq): + r"""Build a contiguous register s from mu and nu. + + $$ + s = \nu (\nu + 1) / 2 + \mu + $$ + + Assuming nu is zero indexed (in contrast to the THC paper which assumes 1, + hence the slightly different formula). + + Args: + bitsize: number of bits for mu and nu registers. + s_bitsize: Number of bits for contiguous register. + + Registers: + mu: input register + nu: input register + s: output contiguous register + + References: + (Even more efficient quantum computations of chemistry through + tensor hypercontraction)[https://arxiv.org/pdf/2011.03494.pdf] Eq. 29. + """ + + bitsize: int + s_bitsize: int + + @cached_property + def signature(self) -> Signature: + return Signature( + [ + Register("mu", bitsize=self.bitsize), + Register("nu", bitsize=self.bitsize), + Register("s", bitsize=self.s_bitsize), + ] + ) + + def on_classical_vals( + self, mu: 'ClassicalValT', nu: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + return {'mu': mu, 'nu': nu, 's': nu * (nu + 1) // 2 + mu} + + def t_complexity(self) -> 'cirq_ft.TComplexity': + num_toffoli = self.bitsize**2 + self.bitsize - 1 + return TComplexity(t=4 * num_toffoli) + + def bloq_counts( + self, ssa: Optional['SympySymbolAllocator'] = None + ) -> Set[Tuple[Union[int, sympy.Expr], Bloq]]: + return {(4 * (self.bitsize**2 + self.bitsize - 1), TGate())} diff --git a/qualtran/bloqs/arithmetic_test.py b/qualtran/bloqs/arithmetic_test.py index ca3f7476d..d543c6d46 100644 --- a/qualtran/bloqs/arithmetic_test.py +++ b/qualtran/bloqs/arithmetic_test.py @@ -15,7 +15,9 @@ from qualtran import BloqBuilder, Register from qualtran.bloqs.arithmetic import ( Add, + EqualsAConstant, GreaterThan, + GreaterThanConstant, MultiplyTwoReals, OutOfPlaceAdder, Product, @@ -23,6 +25,7 @@ Square, SquareRealNumber, SumOfSquares, + ToContiguousIndex, ) from qualtran.testing import execute_notebook @@ -57,6 +60,24 @@ def _make_greater_than(): return GreaterThan(bitsize=4) +def _make_greater_than_constant(): + from qualtran.bloqs.arithmetic import GreaterThanConstant + + return GreaterThanConstant(bitsize=4, val=13) + + +def _make_equals_a_constant(): + from qualtran.bloqs.arithmetic import EqualsAConstant + + return EqualsAConstant(bitsize=4, val=13) + + +def _make_to_contiguous_index(): + from qualtran.bloqs.arithmetic import ToContiguousIndex + + return ToContiguousIndex(bitsize=4, s_bitsize=8) + + def _make_scale_int_by_real(): from qualtran.bloqs.arithmetic import ScaleIntByReal @@ -152,9 +173,37 @@ def test_greater_than(): q0 = bb.add_register('a', bitsize) q1 = bb.add_register('b', bitsize) anc = bb.add_register('result', 1) - q0, q1, anc = bb.add(GreaterThan(bitsize), a=q0, b=q1, result=anc) + q0, q1, anc = bb.add(GreaterThan(bitsize), a=q0, b=q1, target=anc) cbloq = bb.finalize(a=q0, b=q1, result=anc) +def test_greater_than_constant(): + bb = BloqBuilder() + bitsize = 5 + q0 = bb.add_register('x', bitsize) + anc = bb.add_register('result', 1) + q0, anc = bb.add(GreaterThanConstant(bitsize, 17), x=q0, target=anc) + cbloq = bb.finalize(x=q0, result=anc) + + +def test_equals_a_constant(): + bb = BloqBuilder() + bitsize = 5 + q0 = bb.add_register('x', bitsize) + anc = bb.add_register('result', 1) + q0, anc = bb.add(EqualsAConstant(bitsize, 17), x=q0, target=anc) + cbloq = bb.finalize(x=q0, result=anc) + + +def test_to_contiguous_index(): + bb = BloqBuilder() + bitsize = 5 + q0 = bb.add_register('mu', bitsize) + q1 = bb.add_register('nu', bitsize) + out = bb.add_register('s', 1) + q0, q1, out = bb.add(ToContiguousIndex(bitsize, 2 * bitsize), mu=q0, nu=q1, s=out) + cbloq = bb.finalize(mu=q0, nu=q1, s=out) + + def test_notebook(): execute_notebook('arithmetic') diff --git a/qualtran/bloqs/sorting_test.py b/qualtran/bloqs/sorting_test.py index 22477953c..d7b1d4343 100644 --- a/qualtran/bloqs/sorting_test.py +++ b/qualtran/bloqs/sorting_test.py @@ -32,7 +32,7 @@ def _make_bitonic_sort(): def test_comparator(): bloq = Comparator(4) - assert bloq.t_complexity().t == 88 + assert bloq.t_complexity().t == 88 - 4 with pytest.raises(NotImplementedError): bloq.decompose_bloq() @@ -41,7 +41,7 @@ def test_bitonic_sort(): bitsize = 4 k = 8 bloq = BitonicSort(bitsize, k) - assert bloq.t_complexity().t == 8 * 9 * 88 + assert bloq.t_complexity().t == 8 * 9 * (88 - 4) with pytest.raises(NotImplementedError): bloq.decompose_bloq()