diff --git a/qualtran/bloqs/block_encoding/chebyshev_polynomial.ipynb b/qualtran/bloqs/block_encoding/chebyshev_polynomial.ipynb index 75ecd5d57..68c145578 100644 --- a/qualtran/bloqs/block_encoding/chebyshev_polynomial.ipynb +++ b/qualtran/bloqs/block_encoding/chebyshev_polynomial.ipynb @@ -36,26 +36,26 @@ }, "source": [ "## `ChebyshevPolynomial`\n", - "Block encoding of $T_j[H]$ where $T_j$ is the $j$-th Chebyshev polynomial.\n", + "Block encoding of $T_j[A]$ where $T_j$ is the $j$-th Chebyshev polynomial.\n", "\n", - "Here H is a Hamiltonian with spectral norm $|H| \\le 1$, we assume we have\n", + "Here $A$ is a Hermitian matrix with spectral norm $|A| \\le 1$, we assume we have\n", "an $n_L$ qubit ancilla register, and assume that $j > 0$ to avoid block\n", "encoding the identity operator.\n", "\n", "Recall:\n", "\n", "\\begin{align*}\n", - " T_0[H] &= \\mathbb{1} \\\\\n", - " T_1[H] &= H \\\\\n", - " T_2[H] &= 2 H^2 - \\mathbb{1} \\\\\n", - " T_3[H] &= 4 H^3 - 3 H \\\\\n", + " T_0[A] &= I \\\\\n", + " T_1[A] &= A \\\\\n", + " T_2[A] &= 2 A^2 - I \\\\\n", + " T_3[A] &= 4 A^3 - 3 A \\\\\n", " &\\dots\n", "\\end{align*}\n", "\n", "See https://github.com/quantumlib/Qualtran/issues/984 for an alternative.\n", "\n", "#### Parameters\n", - " - `block_encoding`: Block encoding of a Hamiltonian $H$, $\\mathcal{B}[H]$. Assumes the $|G\\rangle$ state of the block encoding is the identity operator.\n", + " - `block_encoding`: Block encoding of a Hermitian matrix $A$, $\\mathcal{B}[A]$. Assumes the $|G\\rangle$ state of the block encoding is the identity operator.\n", " - `order`: order of Chebychev polynomial. \n", "\n", "#### References\n", @@ -87,60 +87,38 @@ { "cell_type": "code", "execution_count": null, - "id": "948022f5", + "id": "76559ac1", "metadata": { - "cq.autogen": "ChebyshevPolynomial.chebyshev_poly" + "cq.autogen": "ChebyshevPolynomial.chebyshev_poly_even" }, "outputs": [], "source": [ - "from qualtran.bloqs.block_encoding import LCUBlockEncodingZeroState\n", - "from qualtran.bloqs.chemistry.hubbard_model.qubitization import PrepareHubbard, SelectHubbard\n", + "from qualtran.bloqs.basic_gates import Hadamard, XGate\n", + "from qualtran.bloqs.block_encoding import LinearCombination, Unitary\n", "\n", - "dim = 3\n", - "select = SelectHubbard(x_dim=dim, y_dim=dim)\n", - "U = 4\n", - "t = 1\n", - "prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, u=U)\n", - "N = dim * dim * 2\n", - "qlambda = 2 * N * t + (N * U) // 2\n", - "block_bloq = LCUBlockEncodingZeroState(\n", - " select=select, prepare=prepare, alpha=qlambda, epsilon=0.0\n", - ")\n", - "chebyshev_poly = ChebyshevPolynomial(block_bloq, order=3)" + "bloq = LinearCombination((Unitary(XGate()), Unitary(Hadamard())), (1.0, 1.0), lambd_bits=1)\n", + "chebyshev_poly_even = ChebyshevPolynomial(bloq, order=4)" ] }, { "cell_type": "code", "execution_count": null, - "id": "5c2de09f", + "id": "1d85b9c1", "metadata": { - "cq.autogen": "ChebyshevPolynomial.black_box_chebyshev_poly" + "cq.autogen": "ChebyshevPolynomial.chebyshev_poly_odd" }, "outputs": [], "source": [ - "from qualtran.bloqs.block_encoding import (\n", - " BlackBoxPrepare,\n", - " BlackBoxSelect,\n", - " LCUBlockEncodingZeroState,\n", - ")\n", - "from qualtran.bloqs.chemistry.hubbard_model.qubitization import PrepareHubbard, SelectHubbard\n", + "from qualtran.bloqs.basic_gates import Hadamard\n", + "from qualtran.bloqs.block_encoding import Unitary\n", "\n", - "dim = 3\n", - "select = SelectHubbard(x_dim=dim, y_dim=dim)\n", - "U = 4\n", - "t = 1\n", - "prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, u=U)\n", - "N = dim * dim * 2\n", - "qlambda = 2 * N * t + (N * U) // 2\n", - "black_box_block_bloq = LCUBlockEncodingZeroState(\n", - " select=BlackBoxSelect(select), prepare=BlackBoxPrepare(prepare), alpha=qlambda, epsilon=0.0\n", - ")\n", - "black_box_chebyshev_poly = ChebyshevPolynomial(black_box_block_bloq, order=3)" + "bloq = Unitary(Hadamard())\n", + "chebyshev_poly_odd = ChebyshevPolynomial(bloq, order=5)" ] }, { "cell_type": "markdown", - "id": "feb63ede", + "id": "9dcdb85f", "metadata": { "cq.autogen": "ChebyshevPolynomial.graphical_signature.md" }, @@ -151,20 +129,20 @@ { "cell_type": "code", "execution_count": null, - "id": "5c240f74", + "id": "70740244", "metadata": { "cq.autogen": "ChebyshevPolynomial.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([chebyshev_poly, black_box_chebyshev_poly],\n", - " ['`chebyshev_poly`', '`black_box_chebyshev_poly`'])" + "show_bloqs([chebyshev_poly_even, chebyshev_poly_odd],\n", + " ['`chebyshev_poly_even`', '`chebyshev_poly_odd`'])" ] }, { "cell_type": "markdown", - "id": "9ae67df3", + "id": "5ceaeb9b", "metadata": { "cq.autogen": "ChebyshevPolynomial.call_graph.md" }, @@ -175,16 +153,16 @@ { "cell_type": "code", "execution_count": null, - "id": "8c7b9d94", + "id": "d258bae7", "metadata": { "cq.autogen": "ChebyshevPolynomial.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "chebyshev_poly_g, chebyshev_poly_sigma = chebyshev_poly.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(chebyshev_poly_g)\n", - "show_counts_sigma(chebyshev_poly_sigma)" + "chebyshev_poly_even_g, chebyshev_poly_even_sigma = chebyshev_poly_even.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(chebyshev_poly_even_g)\n", + "show_counts_sigma(chebyshev_poly_even_sigma)" ] } ], @@ -195,7 +173,8 @@ "name": "python3" }, "language_info": { - "name": "python" + "name": "python", + "version": "3.11.2" } }, "nbformat": 4, diff --git a/qualtran/bloqs/block_encoding/chebyshev_polynomial.py b/qualtran/bloqs/block_encoding/chebyshev_polynomial.py index 4a3007c21..7143a425e 100644 --- a/qualtran/bloqs/block_encoding/chebyshev_polynomial.py +++ b/qualtran/bloqs/block_encoding/chebyshev_polynomial.py @@ -13,40 +13,52 @@ # limitations under the License. from functools import cached_property -from typing import Dict, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, Set, Tuple, TYPE_CHECKING import attrs -from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, Register, Signature, SoquetT -from qualtran.bloqs.block_encoding.lcu_block_encoding import LCUBlockEncodingZeroState -from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare +from qualtran import ( + bloq_example, + BloqBuilder, + BloqDocSpec, + CtrlSpec, + DecomposeTypeError, + QAny, + Register, + Signature, + SoquetT, +) +from qualtran.bloqs.basic_gates.global_phase import GlobalPhase +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle +from qualtran.symbolics import is_symbolic, SymbolicFloat, SymbolicInt if TYPE_CHECKING: from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @attrs.frozen -class ChebyshevPolynomial(Bloq): - r"""Block encoding of $T_j[H]$ where $T_j$ is the $j$-th Chebyshev polynomial. +class ChebyshevPolynomial(BlockEncoding): + r"""Block encoding of $T_j[A]$ where $T_j$ is the $j$-th Chebyshev polynomial. - Here H is a Hamiltonian with spectral norm $|H| \le 1$, we assume we have + Here $A$ is a Hermitian matrix with spectral norm $|A| \le 1$, we assume we have an $n_L$ qubit ancilla register, and assume that $j > 0$ to avoid block encoding the identity operator. Recall: \begin{align*} - T_0[H] &= \mathbb{1} \\ - T_1[H] &= H \\ - T_2[H] &= 2 H^2 - \mathbb{1} \\ - T_3[H] &= 4 H^3 - 3 H \\ + T_0[A] &= I \\ + T_1[A] &= A \\ + T_2[A] &= 2 A^2 - I \\ + T_3[A] &= 4 A^3 - 3 A \\ &\dots \end{align*} See https://github.com/quantumlib/Qualtran/issues/984 for an alternative. Args: - block_encoding: Block encoding of a Hamiltonian $H$, $\mathcal{B}[H]$. + block_encoding: Block encoding of a Hermitian matrix $A$, $\mathcal{B}[A]$. Assumes the $|G\rangle$ state of the block encoding is the identity operator. order: order of Chebychev polynomial. @@ -55,104 +67,116 @@ class ChebyshevPolynomial(Bloq): von Burg et al. 2007. Page 45; Theorem 1. """ - block_encoding: LCUBlockEncodingZeroState + block_encoding: BlockEncoding order: int def __attrs_post_init__(self): if self.order < 1: raise ValueError(f"order must be greater >= 1. Found {self.order}.") - def pretty_name(self) -> str: - return f"T_{self.order}[{self.block_encoding.pretty_name()}]" - @cached_property def signature(self) -> Signature: - return Signature( - [ - *self.block_encoding.selection_registers, - *self.block_encoding.junk_registers, - *self.block_encoding.target_registers, - ] + return Signature.build_from_dtypes( + system=QAny(self.system_bitsize), + ancilla=QAny(self.ancilla_bitsize), # if ancilla_bitsize is 0, not present + resource=QAny(self.resource_bitsize), # if resource_bitsize is 0, not present ) - def build_reflection_bloq(self) -> 'ReflectionUsingPrepare': - refl_bitsizes = tuple(r.bitsize for r in self.block_encoding.selection_registers) - return ReflectionUsingPrepare.reflection_around_zero( - bitsizes=refl_bitsizes, global_phase=-1 + def pretty_name(self) -> str: + return f"B[T_{self.order}({self.block_encoding.pretty_name()})]" + + @property + def system_bitsize(self) -> SymbolicInt: + return self.block_encoding.system_bitsize + + @property + def ancilla_bitsize(self) -> SymbolicInt: + return self.block_encoding.ancilla_bitsize + + @property + def resource_bitsize(self) -> SymbolicInt: + return self.block_encoding.resource_bitsize + + @property + def alpha(self) -> SymbolicFloat: + return self.block_encoding.alpha**self.order + + @property + def epsilon(self) -> SymbolicFloat: + return self.block_encoding.epsilon * self.order + + @property + def target_registers(self) -> Tuple[Register, ...]: + return tuple(self.signature.rights()) + + @property + def junk_registers(self) -> Tuple[Register, ...]: + return (self.signature.get_right("resource"),) if self.resource_bitsize > 0 else () + + @property + def selection_registers(self) -> Tuple[Register, ...]: + return (self.signature.get_right("ancilla"),) if self.ancilla_bitsize > 0 else () + + @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 + + @cached_property + def reflection_bloq(self): + return GlobalPhase(exponent=1).controlled( + ctrl_spec=CtrlSpec(qdtypes=QAny(self.ancilla_bitsize), cvs=0) ) - def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: SoquetT) -> Dict[str, 'SoquetT']: - # includes selection registers and any selection registers used by PREPARE - soqs |= bb.add_d(self.block_encoding, **soqs) - - def _extract_soqs( - to_regs: Union[Signature, Tuple[Register, ...]], - from_regs: Union[Signature, Tuple[Register, ...]], - reg_map: Dict[str, 'SoquetT'], - ) -> Dict[str, 'SoquetT']: - return {t.name: reg_map[f.name] for t, f in zip(to_regs, from_regs)} - - refl_bloq = self.build_reflection_bloq() - for iorder in range(1, self.order): - refl_regs = _extract_soqs( - refl_bloq.signature, self.block_encoding.selection_registers, soqs - ) - refl_regs |= bb.add_d(self.build_reflection_bloq(), **refl_regs) - soqs |= _extract_soqs( - self.block_encoding.selection_registers, refl_bloq.signature, refl_regs - ) + def build_composite_bloq(self, bb: BloqBuilder, **soqs: SoquetT) -> Dict[str, SoquetT]: + if is_symbolic(self.ancilla_bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") + for _ in range(self.order // 2): + if self.ancilla_bitsize > 0: + soqs["ancilla"] = bb.add(self.reflection_bloq, ctrl=soqs["ancilla"]) + soqs |= bb.add_d(self.block_encoding, **soqs) + if self.ancilla_bitsize > 0: + soqs["ancilla"] = bb.add(self.reflection_bloq, ctrl=soqs["ancilla"]) + soqs |= bb.add_d(self.block_encoding.adjoint(), **soqs) + if self.order % 2 == 1: + if self.ancilla_bitsize > 0: + soqs["ancilla"] = bb.add(self.reflection_bloq, ctrl=soqs["ancilla"]) soqs |= bb.add_d(self.block_encoding, **soqs) return soqs def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: n = self.order - return {(self.build_reflection_bloq(), n - 1), (self.block_encoding, n)} + s: Set['BloqCountT'] = { + (self.block_encoding, n // 2 + n % 2), + (self.block_encoding.adjoint(), n // 2), + } + if is_symbolic(self.ancilla_bitsize) or self.ancilla_bitsize > 0: + s.add((self.reflection_bloq, n - n % 2)) + return s @bloq_example -def _chebyshev_poly() -> ChebyshevPolynomial: - from qualtran.bloqs.block_encoding import LCUBlockEncodingZeroState - from qualtran.bloqs.chemistry.hubbard_model.qubitization import PrepareHubbard, SelectHubbard - - dim = 3 - select = SelectHubbard(x_dim=dim, y_dim=dim) - U = 4 - t = 1 - prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, u=U) - N = dim * dim * 2 - qlambda = 2 * N * t + (N * U) // 2 - block_bloq = LCUBlockEncodingZeroState( - select=select, prepare=prepare, alpha=qlambda, epsilon=0.0 - ) - chebyshev_poly = ChebyshevPolynomial(block_bloq, order=3) - return chebyshev_poly +def _chebyshev_poly_even() -> ChebyshevPolynomial: + from qualtran.bloqs.basic_gates import Hadamard, XGate + from qualtran.bloqs.block_encoding import LinearCombination, Unitary + + bloq = LinearCombination((Unitary(XGate()), Unitary(Hadamard())), (1.0, 1.0), lambd_bits=1) + chebyshev_poly_even = ChebyshevPolynomial(bloq, order=4) + return chebyshev_poly_even @bloq_example -def _black_box_chebyshev_poly() -> ChebyshevPolynomial: - from qualtran.bloqs.block_encoding import ( - BlackBoxPrepare, - BlackBoxSelect, - LCUBlockEncodingZeroState, - ) - from qualtran.bloqs.chemistry.hubbard_model.qubitization import PrepareHubbard, SelectHubbard - - dim = 3 - select = SelectHubbard(x_dim=dim, y_dim=dim) - U = 4 - t = 1 - prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, u=U) - N = dim * dim * 2 - qlambda = 2 * N * t + (N * U) // 2 - black_box_block_bloq = LCUBlockEncodingZeroState( - select=BlackBoxSelect(select), prepare=BlackBoxPrepare(prepare), alpha=qlambda, epsilon=0.0 - ) - black_box_chebyshev_poly = ChebyshevPolynomial(black_box_block_bloq, order=3) - return black_box_chebyshev_poly +def _chebyshev_poly_odd() -> ChebyshevPolynomial: + from qualtran.bloqs.basic_gates import Hadamard + from qualtran.bloqs.block_encoding import Unitary + + bloq = Unitary(Hadamard()) + chebyshev_poly_odd = ChebyshevPolynomial(bloq, order=5) + return chebyshev_poly_odd _CHEBYSHEV_BLOQ_DOC = BloqDocSpec( - bloq_cls=ChebyshevPolynomial, - import_line='from qualtran.bloqs.block_encoding import ChebyshevPolynomial', - examples=(_chebyshev_poly, _black_box_chebyshev_poly), + bloq_cls=ChebyshevPolynomial, examples=(_chebyshev_poly_even, _chebyshev_poly_odd) ) diff --git a/qualtran/bloqs/block_encoding/chebyshev_polynomial_test.py b/qualtran/bloqs/block_encoding/chebyshev_polynomial_test.py index df89d02da..2cfddf806 100644 --- a/qualtran/bloqs/block_encoding/chebyshev_polynomial_test.py +++ b/qualtran/bloqs/block_encoding/chebyshev_polynomial_test.py @@ -11,22 +11,71 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.bloqs.basic_gates import TGate + +import numpy as np + +from qualtran import BloqBuilder +from qualtran.bloqs.basic_gates import Hadamard, IntEffect, IntState, XGate from qualtran.bloqs.block_encoding.chebyshev_polynomial import ( - _black_box_chebyshev_poly, - _chebyshev_poly, + _chebyshev_poly_even, + _chebyshev_poly_odd, ) +from qualtran.symbolics import is_symbolic +from qualtran.testing import assert_equivalent_bloq_example_counts + + +def test_chebyshev_poly_even(bloq_autotester): + bloq_autotester(_chebyshev_poly_even) + + +def test_chebyshev_poly_even_counts(): + assert_equivalent_bloq_example_counts(_chebyshev_poly_even) + + +def test_chebyshev_poly_odd(bloq_autotester): + bloq_autotester(_chebyshev_poly_odd) + + +def test_chebyshev_poly_odd_counts(): + assert_equivalent_bloq_example_counts(_chebyshev_poly_odd) + + +def test_chebyshev_poly_even_tensors(): + def t4(x): + return 8 * np.linalg.matrix_power(x, 4) - 8 * np.linalg.matrix_power(x, 2) + np.eye(2) + + from_gate = t4((XGate().tensor_contract() + Hadamard().tensor_contract()) / 2.0) + bloq = _chebyshev_poly_even() + assert ( + not is_symbolic(bloq.system_bitsize) + and not is_symbolic(bloq.ancilla_bitsize) + and not is_symbolic(bloq.resource_bitsize) + ) -def test_chebyshev(bloq_autotester): - bloq_autotester(_chebyshev_poly) + bb = BloqBuilder() + system = bb.add_register("system", bloq.system_bitsize) + ancilla = bb.add(IntState(0, bloq.ancilla_bitsize)) + resource = bb.add(IntState(0, bloq.resource_bitsize)) + system, ancilla, resource = bb.add(bloq, system=system, ancilla=ancilla, resource=resource) + bb.add(IntEffect(0, bloq.ancilla_bitsize), val=ancilla) + bb.add(IntEffect(0, bloq.resource_bitsize), val=resource) + bloq = bb.finalize(system=system) + from_tensors = bloq.tensor_contract() + np.testing.assert_allclose(from_gate, from_tensors, atol=1e-14) -def test_black_box_chebyshev(bloq_autotester): - bloq_autotester(_black_box_chebyshev_poly) +def test_chebyshev_poly_odd_tensors(): + def t5(x): + return 16 * np.linalg.matrix_power(x, 5) - 20 * np.linalg.matrix_power(x, 3) + 5 * x + from_gate = t5(Hadamard().tensor_contract()) -def test_chebyshev_t_counts(): - counts = _chebyshev_poly().call_graph()[1] - counts_decomp = _chebyshev_poly().decompose_bloq().call_graph()[1] - assert counts[TGate()] == counts_decomp[TGate()] + bloq = _chebyshev_poly_odd() + assert not is_symbolic(bloq.system_bitsize) + bb = BloqBuilder() + system = bb.add_register("system", bloq.system_bitsize) + system = bb.add(bloq, system=system) + bloq = bb.finalize(system=system) + from_tensors = bloq.tensor_contract() + np.testing.assert_allclose(from_gate, from_tensors, atol=1e-14) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index 6f1303aba..5d3524750 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -110,6 +110,7 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'permutation_cycle_symb', 'explicit_matrix_block_encoding', # cannot serialize AutoPartition 'symmetric_banded_matrix_block_encoding', # cannot serialize AutoPartition + 'chebyshev_poly_even', ]: pytest.xfail("Skipping serialization test for bloq examples that cannot yet be serialized.")