diff --git a/dev_tools/autogenerate-bloqs-notebooks.py b/dev_tools/autogenerate-bloqs-notebooks.py index d400b177e..2755c558d 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_scale_int_by_real), + BloqNbSpec(qualtran.bloqs.arithmetic_test._make_multiply_two_reals), + BloqNbSpec(qualtran.bloqs.arithmetic_test._make_square_real_number), ], directory=f'{SOURCE_DIR}/bloqs', ), diff --git a/qualtran/bloqs/arithmetic.ipynb b/qualtran/bloqs/arithmetic.ipynb index cfaf5806c..0d0dd3f43 100644 --- a/qualtran/bloqs/arithmetic.ipynb +++ b/qualtran/bloqs/arithmetic.ipynb @@ -71,7 +71,7 @@ }, "source": [ "## `Product`\n", - "Compute the product of an `n` and `m` bit integer.\n", + "Compute the product of an `n` and `m` bit binary number.\n", "\n", "Implements $U|a\\rangle|b\\rangle|0\\rangle -\\rightarrow\n", "|a\\rangle|b\\rangle|a\\times b\\rangle$ using $2nm-n$ Toffolis.\n", @@ -112,9 +112,9 @@ }, "source": [ "## `Square`\n", - "Square an n-bit number.\n", + "Square an n-bit binary number.\n", "\n", - "Implements $U|a\\rangle|0\\rangle \\rightarrow |a\\rangle|a^2\\rangle$ using $4n - 4 T$ gates.\n", + "Implements $U|a\\rangle|0\\rangle \\rightarrow |a\\rangle|a^2\\rangle$ using $n^2 - n$ Toffolis.\n", "\n", "#### Parameters\n", " - `bitsize`: Number of bits used to represent the integer to be squared. The result is stored in a register of size 2*bitsize. \n", @@ -150,19 +150,21 @@ }, "source": [ "## `SumOfSquares`\n", - "Compute the sum of squares of k n-bit numbers.\n", + "Compute the sum of squares of k n-bit binary numbers.\n", "\n", "Implements $U|a\\rangle|b\\rangle\\dots k\\rangle|0\\rangle \\rightarrow\n", " |a\\rangle|b\\rangle\\dots|k\\rangle|a^2+b^2+\\dots k^2\\rangle$ using\n", " $4 k n^2 T$ gates.\n", "\n", + "The number of bits required by the output register is 2*bitsize + ceil(log2(k)).\n", + "\n", "#### Parameters\n", " - `bitsize`: Number of bits used to represent each of the k integers.\n", " - `k`: The number of integers we want to square. \n", "\n", "#### Registers\n", " - `input`: k n-bit registers.\n", - " - `result`: 2 * bitsize + 1 sized output register. \n", + " - `result`: 2 * bitsize + ceil(log2(k)) sized output register. \n", "\n", "#### References\n", "[Fault-Tolerant Quantum Simulations of Chemistry in First Quantization](https://arxiv.org/abs/2105.12767) pg 80 give a Toffoli complexity for squaring.\n" @@ -223,6 +225,142 @@ "bloq = GreaterThan(bitsize=4)\n", "show_bloq(bloq)" ] + }, + { + "cell_type": "markdown", + "id": "1c63d263", + "metadata": { + "cq.autogen": "_make_scale_int_by_real.md" + }, + "source": [ + "## `ScaleIntByReal`\n", + "Scale an integer by fixed-point representation of a real number.\n", + "\n", + "i.e.\n", + "\n", + "$$\n", + " |r\\rangle|i\\rangle|0\\rangle \\rightarrow |r\\rangle|i\\rangle|r \\times i\\rangle\n", + "$$\n", + "\n", + "The real number is assumed to be in the range [0, 1).\n", + "\n", + "#### Parameters\n", + " - `r_bitsize`: Number of bits used to represent the real number.\n", + " - `i_bitsize`: Number of bits used to represent the integer. \n", + "\n", + "#### Registers\n", + " - `- real_in`: r_bitsize-sized input register.\n", + " - `- int_in`: i_bitsize-sized input register.\n", + " - `- result`: r_bitsize output register \n", + "\n", + "#### References\n", + "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] (https://arxiv.org/pdf/2007.07391.pdf) pg 70.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4538d32b", + "metadata": { + "cq.autogen": "_make_scale_int_by_real.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import ScaleIntByReal\n", + "\n", + "bloq = ScaleIntByReal(r_bitsize=8, i_bitsize=12)\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "6247c0bd", + "metadata": { + "cq.autogen": "_make_multiply_two_reals.md" + }, + "source": [ + "## `MultiplyTwoReals`\n", + "Multiply two fixed-point representations of real numbers\n", + "\n", + "i.e.\n", + "\n", + "$$\n", + " |a\\rangle|b\\rangle|0\\rangle \\rightarrow |a\\rangle|b\\rangle|a \\times b\\rangle\n", + "$$\n", + "\n", + "The real numbers are assumed to be in the range [0, 1).\n", + "\n", + "#### Parameters\n", + " - `bitsize`: Number of bits used to represent the real number. \n", + "\n", + "#### Registers\n", + " - `- a`: bitsize-sized input register.\n", + " - `- b`: bitsize-sized input register.\n", + " - `- result`: bitsize output register \n", + "\n", + "#### References\n", + "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] (https://arxiv.org/pdf/2007.07391.pdf) pg 71.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b8be106", + "metadata": { + "cq.autogen": "_make_multiply_two_reals.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import MultiplyTwoReals\n", + "\n", + "bloq = MultiplyTwoReals(bitsize=10)\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "7b0a73bb", + "metadata": { + "cq.autogen": "_make_square_real_number.md" + }, + "source": [ + "## `SquareRealNumber`\n", + "Square a fixed-point representation of a real number\n", + "\n", + "i.e.\n", + "\n", + "$$\n", + " |a\\rangle|0\\rangle \\rightarrow |a\\rangle|a^2\\rangle\n", + "$$\n", + "\n", + "The real numbers are assumed to be in the range [0, 1).\n", + "\n", + "#### Parameters\n", + " - `bitsize`: Number of bits used to represent the real number. \n", + "\n", + "#### Registers\n", + " - `- a`: bitsize-sized input register.\n", + " - `- b`: bitsize-sized input register.\n", + " - `- result`: bitsize output register \n", + "\n", + "#### References\n", + "[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization ](https://arxiv.org/pdf/2007.07391.pdf) pg 74.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec6335d9", + "metadata": { + "cq.autogen": "_make_square_real_number.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.arithmetic import SquareRealNumber\n", + "\n", + "bloq = SquareRealNumber(bitsize=10)\n", + "show_bloq(bloq)" + ] } ], "metadata": { @@ -241,7 +379,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.6" } }, "nbformat": 4, diff --git a/qualtran/bloqs/arithmetic.py b/qualtran/bloqs/arithmetic.py index 65ff27598..57ac5810a 100644 --- a/qualtran/bloqs/arithmetic.py +++ b/qualtran/bloqs/arithmetic.py @@ -12,10 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Optional, Set, Tuple, TYPE_CHECKING + from attrs import frozen from cirq_ft import TComplexity -from qualtran import Bloq, Register, Signature +from qualtran import Bloq, Register, Side, Signature +from qualtran.bloqs.basic_gates import TGate +from qualtran.bloqs.util_bloqs import ArbitraryClifford + +if TYPE_CHECKING: + from qualtran.resource_counting import SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT @frozen @@ -50,12 +58,57 @@ def t_complexity(self): num_t_gates = 4 * self.bitsize - 4 return TComplexity(t=num_t_gates, clifford=num_clifford) + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + num_clifford = (self.bitsize - 2) * 19 + 16 + num_t_gates = 4 * self.bitsize - 4 + return {(num_t_gates, TGate()), (num_clifford, ArbitraryClifford(n=1))} + + +@frozen +class OutOfPlaceAdder(Bloq): + r"""An n-bit addition gate. + + Implements $U|a\rangle|b\rangle 0\rangle \rightarrow |a\rangle|b\rangle|a+b\rangle$ + using $4n - 4 T$ gates. + + Args: + bitsize: Number of bits used to represent each integer. Must be large + enough to hold the result in the output register of a + b. + + Registers: + - a: A bitsize-sized input register (register a above). + - b: A bitsize-sized input/output register (register b above). + + References: + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648) + """ + + bitsize: int + + @property + def signature(self): + return Signature.build(a=self.bitsize, b=self.bitsize, c=self.bitsize) + + def pretty_name(self) -> str: + return "c = a + b" + + def t_complexity(self): + # extra bitsize cliffords comes from CNOTs before adding: + # yield CNOT.on_each(zip(b, c)) + # yield Add(a, c) + num_clifford = (self.bitsize - 2) * 19 + 16 + self.bitsize + num_t_gates = 4 * self.bitsize - 4 + return TComplexity(t=num_t_gates, clifford=num_clifford) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + return {(1, Add(self.bitsize)), (self.bitsize, ArbitraryClifford(n=2))} + @frozen class Square(Bloq): - r"""Square an n-bit number. + r"""Square an n-bit binary number. - Implements $U|a\rangle|0\rangle \rightarrow |a\rangle|a^2\rangle$ using $4n - 4 T$ gates. + Implements $U|a\rangle|0\rangle \rightarrow |a\rangle|a^2\rangle$ using $n^2 - n$ Toffolis. Args: bitsize: Number of bits used to represent the integer to be squared. The @@ -74,7 +127,9 @@ class Square(Bloq): @property def signature(self): - return Signature.build(a=self.bitsize, result=2 * self.bitsize) + return Signature( + [Register("a", self.bitsize), Register("result", 2 * self.bitsize, side=Side.RIGHT)] + ) def pretty_name(self) -> str: return "a^2" @@ -86,22 +141,28 @@ def t_complexity(self): num_toff = self.bitsize * (self.bitsize - 1) return TComplexity(t=4 * num_toff) + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + num_toff = self.bitsize * (self.bitsize - 1) + return {(4 * num_toff, TGate())} + @frozen class SumOfSquares(Bloq): - r"""Compute the sum of squares of k n-bit numbers. + r"""Compute the sum of squares of k n-bit binary numbers. Implements $U|a\rangle|b\rangle\dots k\rangle|0\rangle \rightarrow |a\rangle|b\rangle\dots|k\rangle|a^2+b^2+\dots k^2\rangle$ using $4 k n^2 T$ gates. + The number of bits required by the output register is 2*bitsize + ceil(log2(k)). + Args: bitsize: Number of bits used to represent each of the k integers. k: The number of integers we want to square. Registers: input: k n-bit registers. - result: 2 * bitsize + 1 sized output register. + result: 2 * bitsize + ceil(log2(k)) sized output register. References: [Fault-Tolerant Quantum Simulations of Chemistry in First @@ -117,7 +178,9 @@ def signature(self): return Signature( [ Register("input", bitsize=self.bitsize, shape=(self.k,)), - Register("result", bitsize=2 * self.bitsize + 1), + Register( + "result", bitsize=2 * self.bitsize + (self.k - 1).bit_length(), side=Side.RIGHT + ), ] ) @@ -133,10 +196,16 @@ def t_complexity(self): num_toff -= 1 return TComplexity(t=4 * num_toff) + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + num_toff = self.k * self.bitsize**2 - self.bitsize + if self.k % 3 == 0: + num_toff -= 1 + return {(4 * num_toff, TGate())} + @frozen class Product(Bloq): - r"""Compute the product of an `n` and `m` bit integer. + r"""Compute the product of an `n` and `m` bit binary number. Implements $U|a\rangle|b\rangle|0\rangle -\rightarrow |a\rangle|b\rangle|a\times b\rangle$ using $2nm-n$ Toffolis. @@ -161,8 +230,12 @@ class Product(Bloq): @property def signature(self): - return Signature.build( - a=self.a_bitsize, b=self.b_bitsize, result=2 * max(self.a_bitsize, self.b_bitsize) + return Signature( + [ + Register("a", self.a_bitsize), + Register("b", self.b_bitsize), + Register("result", 2 * max(self.a_bitsize, self.b_bitsize), side=Side.RIGHT), + ] ) def pretty_name(self) -> str: @@ -175,6 +248,171 @@ def t_complexity(self): num_toff = 2 * self.a_bitsize * self.b_bitsize - max(self.a_bitsize, self.b_bitsize) return TComplexity(t=4 * num_toff) + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + num_toff = 2 * self.a_bitsize * self.b_bitsize - max(self.a_bitsize, self.b_bitsize) + return {(4 * num_toff, TGate())} + + +@frozen +class ScaleIntByReal(Bloq): + r"""Scale an integer by fixed-point representation of a real number. + + i.e. + + $$ + |r\rangle|i\rangle|0\rangle \rightarrow |r\rangle|i\rangle|r \times i\rangle + $$ + + The real number is assumed to be in the range [0, 1). + + Args: + r_bitsize: Number of bits used to represent the real number. + i_bitsize: Number of bits used to represent the integer. + + Registers: + - real_in: r_bitsize-sized input register. + - int_in: i_bitsize-sized input register. + - result: r_bitsize output register + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] + (https://arxiv.org/pdf/2007.07391.pdf) pg 70. + """ + + r_bitsize: int + i_bitsize: int + + @property + def signature(self): + return Signature( + [ + Register("real_in", self.r_bitsize), + Register("int_in", self.i_bitsize), + Register("result", self.r_bitsize, side=Side.RIGHT), + ] + ) + + def pretty_name(self) -> str: + return "r*i" + + def t_complexity(self): + # Eq. D8, we are assuming dA and dB there are assumed as inputs and the + # user has ensured these are large enough for their desired precision. + num_toff = self.r_bitsize * (2 * self.i_bitsize - 1) - self.i_bitsize**2 + return TComplexity(t=4 * num_toff) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + # Eq. D8, we are assuming dA(r_bitsize) and dB(i_bitsize) are inputs and + # the user has ensured these are large enough for their desired + # precision. + num_toff = self.r_bitsize * (2 * self.i_bitsize - 1) - self.i_bitsize**2 + return {(4 * num_toff, TGate())} + + +@frozen +class MultiplyTwoReals(Bloq): + r"""Multiply two fixed-point representations of real numbers + + i.e. + + $$ + |a\rangle|b\rangle|0\rangle \rightarrow |a\rangle|b\rangle|a \times b\rangle + $$ + + The real numbers are assumed to be in the range [0, 1). + + Args: + bitsize: Number of bits used to represent the real number. + + Registers: + - a: bitsize-sized input register. + - b: bitsize-sized input register. + - result: bitsize output register + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] + (https://arxiv.org/pdf/2007.07391.pdf) pg 71. + """ + + bitsize: int + + @property + def signature(self): + return Signature( + [ + Register("a", self.bitsize), + Register("b", self.bitsize), + Register("result", self.bitsize, side=Side.RIGHT), + ] + ) + + def pretty_name(self) -> str: + return "a*b" + + def t_complexity(self): + # Eq. D13, there it is suggested keeping both registers the same size is optimal. + num_toff = self.bitsize**2 - self.bitsize - 1 + return TComplexity(t=4 * num_toff) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + # Eq. D13, there it is suggested keeping both registers the same size is optimal. + num_toff = self.bitsize**2 - self.bitsize - 1 + return {(4 * num_toff, TGate())} + + +@frozen +class SquareRealNumber(Bloq): + r"""Square a fixed-point representation of a real number + + i.e. + + $$ + |a\rangle|0\rangle \rightarrow |a\rangle|a^2\rangle + $$ + + The real numbers are assumed to be in the range [0, 1). + + Args: + bitsize: Number of bits used to represent the real number. + + Registers: + - a: bitsize-sized input register. + - b: bitsize-sized input register. + - result: bitsize output register + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization + ](https://arxiv.org/pdf/2007.07391.pdf) pg 74. + """ + + bitsize: int + + def __attrs_post_init__(self): + if self.bitsize < 3: + raise ValueError("bitsize must be at least 3 for SquareRealNumber bloq to make sense.") + + @property + def signature(self): + return Signature( + [ + Register("a", self.bitsize), + Register("b", self.bitsize), + Register("result", self.bitsize, side=Side.RIGHT), + ] + ) + + def pretty_name(self) -> str: + return "a^2" + + def t_complexity(self): + num_toff = self.bitsize**2 // 2 - 4 + return TComplexity(t=4 * num_toff) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + # Bottom of page 74 + num_toff = self.bitsize**2 // 2 - 4 + return {(4 * num_toff, TGate())} + @frozen class GreaterThan(Bloq): @@ -211,3 +449,6 @@ def t_complexity(self): # See: https://github.com/quantumlib/cirq-qubitization/issues/219 # See: https://github.com/quantumlib/cirq-qubitization/issues/217 return TComplexity(t=8 * self.bitsize) + + def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: + return {(8 * self.bitsize, TGate())} diff --git a/qualtran/bloqs/arithmetic_test.py b/qualtran/bloqs/arithmetic_test.py index 6afdaead6..ca3f7476d 100644 --- a/qualtran/bloqs/arithmetic_test.py +++ b/qualtran/bloqs/arithmetic_test.py @@ -13,7 +13,17 @@ # limitations under the License. from qualtran import BloqBuilder, Register -from qualtran.bloqs.arithmetic import Add, GreaterThan, Product, Square, SumOfSquares +from qualtran.bloqs.arithmetic import ( + Add, + GreaterThan, + MultiplyTwoReals, + OutOfPlaceAdder, + Product, + ScaleIntByReal, + Square, + SquareRealNumber, + SumOfSquares, +) from qualtran.testing import execute_notebook @@ -47,6 +57,24 @@ def _make_greater_than(): return GreaterThan(bitsize=4) +def _make_scale_int_by_real(): + from qualtran.bloqs.arithmetic import ScaleIntByReal + + return ScaleIntByReal(r_bitsize=8, i_bitsize=12) + + +def _make_multiply_two_reals(): + from qualtran.bloqs.arithmetic import MultiplyTwoReals + + return MultiplyTwoReals(bitsize=10) + + +def _make_square_real_number(): + from qualtran.bloqs.arithmetic import SquareRealNumber + + return SquareRealNumber(bitsize=10) + + def test_add(): bb = BloqBuilder() bitsize = 4 @@ -56,12 +84,21 @@ def test_add(): cbloq = bb.finalize(a=a, b=b) +def test_out_of_place_adder(): + bb = BloqBuilder() + bitsize = 4 + q0 = bb.add_register('a', bitsize) + q1 = bb.add_register('b', bitsize) + q2 = bb.add_register('c', bitsize) + a, b, c = bb.add(OutOfPlaceAdder(bitsize), a=q0, b=q1, c=q2) + cbloq = bb.finalize(a=a, b=b, c=c) + + def test_square(): bb = BloqBuilder() bitsize = 4 q0 = bb.add_register('a', bitsize) - q1 = bb.add_register('result', 2 * bitsize) - q0, q1 = bb.add(Square(bitsize), a=q0, result=q1) + q0, q1 = bb.add(Square(bitsize), a=q0) cbloq = bb.finalize(a=q0, result=q1) @@ -70,9 +107,9 @@ def test_sum_of_squares(): bitsize = 4 k = 3 inp = bb.add_register(Register("input", bitsize=bitsize, shape=(k,))) - out = bb.add_register(Register("result", bitsize=2 * bitsize + 1)) - inp, out = bb.add(SumOfSquares(bitsize, k), input=inp, result=out) + inp, out = bb.add(SumOfSquares(bitsize, k), input=inp) cbloq = bb.finalize(input=inp, result=out) + assert SumOfSquares(bitsize, k).signature[1].bitsize == 2 * bitsize + 2 def test_product(): @@ -81,8 +118,31 @@ def test_product(): mbits = 3 q0 = bb.add_register('a', bitsize) q1 = bb.add_register('b', mbits) - q2 = bb.add_register('result', 2 * max(bitsize, mbits)) - q0, q1, q2 = bb.add(Product(bitsize, mbits), a=q0, b=q1, result=q2) + q0, q1, q2 = bb.add(Product(bitsize, mbits), a=q0, b=q1) + cbloq = bb.finalize(a=q0, b=q1, result=q2) + + +def test_scale_int_by_real(): + bb = BloqBuilder() + q0 = bb.add_register('a', 15) + q1 = bb.add_register('b', 8) + q0, q1, q2 = bb.add(ScaleIntByReal(15, 8), real_in=q0, int_in=q1) + cbloq = bb.finalize(a=q0, b=q1, result=q2) + + +def test_multiply_two_reals(): + bb = BloqBuilder() + q0 = bb.add_register('a', 15) + q1 = bb.add_register('b', 15) + q0, q1, q2 = bb.add(MultiplyTwoReals(15), a=q0, b=q1) + cbloq = bb.finalize(a=q0, b=q1, result=q2) + + +def test_square_real_number(): + bb = BloqBuilder() + q0 = bb.add_register('a', 15) + q1 = bb.add_register('b', 15) + q0, q1, q2 = bb.add(SquareRealNumber(15), a=q0, b=q1) cbloq = bb.finalize(a=q0, b=q1, result=q2)