From b1ee83363faf6a1f7bbfaa1ba5f54fdded83f313 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Tue, 29 Oct 2024 01:47:27 +0100 Subject: [PATCH] Add `SparseMatrixHermitian` block-encoding --- .../qualtran_dev_tools/notebook_specs.py | 8 + docs/bloqs/index.rst | 1 + qualtran/bloqs/block_encoding/__init__.py | 1 + .../sparse_matrix_hermitian.ipynb | 204 +++++++++++ .../block_encoding/sparse_matrix_hermitian.py | 316 ++++++++++++++++++ .../sparse_matrix_hermitian_test.py | 118 +++++++ qualtran/conftest.py | 2 + 7 files changed, 650 insertions(+) create mode 100644 qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb create mode 100644 qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py create mode 100644 qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index 00bde2cb8..a35c468ce 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -51,6 +51,7 @@ import qualtran.bloqs.block_encoding.phase import qualtran.bloqs.block_encoding.product import qualtran.bloqs.block_encoding.sparse_matrix +import qualtran.bloqs.block_encoding.sparse_matrix_hermitian import qualtran.bloqs.block_encoding.tensor_product import qualtran.bloqs.block_encoding.unitary import qualtran.bloqs.bookkeeping @@ -740,6 +741,13 @@ module=qualtran.bloqs.block_encoding.sparse_matrix, bloq_specs=[qualtran.bloqs.block_encoding.sparse_matrix._SPARSE_MATRIX_DOC], ), + NotebookSpecV2( + title='Sparse Matrix Hermitian', + module=qualtran.bloqs.block_encoding.sparse_matrix_hermitian, + bloq_specs=[ + qualtran.bloqs.block_encoding.sparse_matrix_hermitian._SPARSE_MATRIX_HERMITIAN_DOC + ], + ), NotebookSpecV2( title='Chebyshev Polynomial', module=qualtran.bloqs.block_encoding.chebyshev_polynomial, diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 7d7f1a27f..d827dfaad 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -130,6 +130,7 @@ Bloqs Library block_encoding/phase.ipynb block_encoding/linear_combination.ipynb block_encoding/sparse_matrix.ipynb + block_encoding/sparse_matrix_hermitian.ipynb block_encoding/chebyshev_polynomial.ipynb block_encoding/lcu_block_encoding.ipynb diff --git a/qualtran/bloqs/block_encoding/__init__.py b/qualtran/bloqs/block_encoding/__init__.py index 6bf24ff4a..03c07b298 100644 --- a/qualtran/bloqs/block_encoding/__init__.py +++ b/qualtran/bloqs/block_encoding/__init__.py @@ -23,5 +23,6 @@ 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.sparse_matrix_hermitian import SparseMatrixHermitian from qualtran.bloqs.block_encoding.tensor_product import TensorProduct from qualtran.bloqs.block_encoding.unitary import Unitary diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb new file mode 100644 index 000000000..117edcce9 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb @@ -0,0 +1,204 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8e5e678f", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Sparse Matrix Hermitian" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70980f2b", + "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": "8db414b7", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.bloq_doc.md" + }, + "source": [ + "## `SparseMatrixHermitian`\n", + "Hermitian Block encoding of a sparse-access Hermitian matrix.\n", + "\n", + "Given column and entry oracles $O_c$ and $O_A$ for an $s$-sparse Hermitian 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", + "a |0> ─┤ ├─ |0> ───────────────────────X────────────────────\n", + " │ │ ┌──┐ | ┌──┐\n", + " │ U │ = │ n│ ┌────┐ ┌────┐ | ┌────┐ ┌────┐ │ n│\n", + "l |0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ O ├─┤ ├─X──|─┤ ├─┤ O* ├─┤H ├─\n", + " │ │ └──┘ | c | │ │ | | │ │ | c | └──┘\n", + " │ │ └────┘ │ O │ │ | │ O* │ └────┘\n", + "b |0> ─┤ ├─ |0> ────────|────┤ A ├─|──X─┤ A ├───|─────────\n", + " | | ┌────┐ | | | | | ┌────┐\n", + " | | | O | | | | | | | O* |\n", + "j |Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X────┤ ├─┤ c ├──────\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", + "For encoding a non-hermitian matrix, or a slightly more efficient (but non Hermitian-encoding)\n", + "of a matrix, use :class:`SparseMatrix` instead.\n", + "\n", + "#### Parameters\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", + " - `is_controlled`: if True, returns the controlled block-encoding. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The single qubit control register. (present only if `is_controlled` is `True`)\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. Proposition 6.8, Fig 6.7.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f31bfd74", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding import SparseMatrixHermitian" + ] + }, + { + "cell_type": "markdown", + "id": "435f31d2", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "720f3f9b", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.sparse_matrix_symb_hermitian_block_encoding" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle\n", + "from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle\n", + "\n", + "n = sympy.Symbol('n', positive=True, integer=True)\n", + "col_oracle = TopLeftRowColumnOracle(system_bitsize=n)\n", + "entry_oracle = UniformSqrtEntryOracle(system_bitsize=n, entry=0.3)\n", + "sparse_matrix_symb_hermitian_block_encoding = SparseMatrixHermitian(\n", + " col_oracle, entry_oracle, eps=0\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70e512ff", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.sparse_matrix_hermitian_block_encoding" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle\n", + "from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle\n", + "\n", + "col_oracle = TopLeftRowColumnOracle(system_bitsize=2)\n", + "entry_oracle = UniformSqrtEntryOracle(system_bitsize=2, entry=0.3)\n", + "sparse_matrix_hermitian_block_encoding = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0)" + ] + }, + { + "cell_type": "markdown", + "id": "6e8d3efa", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96d58575", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([sparse_matrix_symb_hermitian_block_encoding, sparse_matrix_hermitian_block_encoding],\n", + " ['`sparse_matrix_symb_hermitian_block_encoding`', '`sparse_matrix_hermitian_block_encoding`'])" + ] + }, + { + "cell_type": "markdown", + "id": "e0108dfc", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87933b2f", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "sparse_matrix_symb_hermitian_block_encoding_g, sparse_matrix_symb_hermitian_block_encoding_sigma = sparse_matrix_symb_hermitian_block_encoding.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(sparse_matrix_symb_hermitian_block_encoding_g)\n", + "show_counts_sigma(sparse_matrix_symb_hermitian_block_encoding_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py new file mode 100644 index 000000000..43ff03547 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py @@ -0,0 +1,316 @@ +# 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 collections import Counter +from functools import cached_property + +import attrs +import numpy as np +import sympy +from attrs import frozen + +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CtrlSpec, + DecomposeTypeError, + QAny, + QBit, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import CSwap, Ry, Swap +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.block_encoding.sparse_matrix import RowColumnOracle +from qualtran.bloqs.bookkeeping import Partition +from qualtran.bloqs.bookkeeping.auto_partition import AutoPartition, Unused +from qualtran.bloqs.reflections.prepare_identity import PrepareIdentity +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.bloqs.state_preparation.prepare_uniform_superposition import ( + PrepareUniformSuperposition, +) +from qualtran.resource_counting import BloqCountT, SympySymbolAllocator +from qualtran.symbolics import is_symbolic, SymbolicFloat, SymbolicInt +from qualtran.symbolics.math_funcs import bit_length + + +class SqrtEntryOracle(Bloq): + r"""Oracle specifying the sqrt of entries of a sparse-access matrix. + + In the reference, this is the interface of + $$O_A : \ket{0}\ket{i}\ket{j} \mapsto (\sqrt{A_{ij}} \ket{0} + \sqrt{1 - |A_{ij}|}\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.""" + + @property + @abc.abstractmethod + def epsilon(self) -> SymbolicFloat: + """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 SparseMatrixHermitian(BlockEncoding): + r"""Hermitian Block encoding of a sparse-access Hermitian matrix. + + Given column and entry oracles $O_c$ and $O_A$ for an $s$-sparse Hermitian 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: + ``` + ┌────┐ + a |0> ─┤ ├─ |0> ───────────────────────X──────────────────── + │ │ ┌──┐ | ┌──┐ + │ U │ = │ n│ ┌────┐ ┌────┐ | ┌────┐ ┌────┐ │ n│ + l |0^n> ─┤ A ├─ |0^n> ─┤H ├─┤ O ├─┤ ├─X──|─┤ ├─┤ O* ├─┤H ├─ + │ │ └──┘ | c | │ │ | | │ │ | c | └──┘ + │ │ └────┘ │ O │ │ | │ O* │ └────┘ + b |0> ─┤ ├─ |0> ────────|────┤ A ├─|──X─┤ A ├───|───────── + | | ┌────┐ | | | | | ┌────┐ + | | | O | | | | | | | O* | + j |Psi> ─┤ ├─ |Psi> ──────┤ c ├─┤ ├─X────┤ ├─┤ c ├────── + └────┘ └────┘ └────┘ └────┘ └────┘ + ``` + + 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. + + For encoding a non-hermitian matrix, or a slightly more efficient (but non Hermitian-encoding) + of a matrix, use :class:`SparseMatrix` instead. + + Args: + 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. + is_controlled: if True, returns the controlled block-encoding. + + Registers: + ctrl: The single qubit control register. (present only if `is_controlled` is `True`) + 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. Proposition 6.8, Fig 6.7. + """ + + col_oracle: RowColumnOracle + entry_oracle: SqrtEntryOracle + eps: SymbolicFloat + is_controlled: bool = False + + def __attrs_post_init__(self): + if self.col_oracle.system_bitsize != self.entry_oracle.system_bitsize: + raise ValueError("column and entry oracles must have same bitsize") + + @cached_property + def signature(self) -> Signature: + n_ctrls = 1 if self.is_controlled else 0 + + return Signature.build_from_dtypes( + ctrl=QAny(n_ctrls), + 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 __str__(self) -> str: + return "B[SparseMatrixHermitian]" + + @cached_property + def alpha(self) -> SymbolicFloat: + return self.col_oracle.num_nonzero + + @cached_property + def ancilla_bitsize(self) -> SymbolicInt: + return self.system_bitsize + 2 + + @cached_property + def resource_bitsize(self) -> SymbolicInt: + return 0 + + @cached_property + def epsilon(self) -> SymbolicFloat: + return self.eps + + @property + def signal_state(self) -> BlackBoxPrepare: + return BlackBoxPrepare(PrepareIdentity.from_bitsizes([self.ancilla_bitsize])) + + @cached_property + def diffusion(self): + unused = self.system_bitsize - bit_length(self.col_oracle.num_nonzero - 1) + return AutoPartition( + PrepareUniformSuperposition(n=self.col_oracle.num_nonzero), + partitions=[ + (Register("target", QAny(self.system_bitsize)), [Unused(unused), "target"]) + ], + ) + + def build_call_graph(self, ssa: SympySymbolAllocator) -> set[BloqCountT]: + counts = Counter[Bloq]() + + counts[self.diffusion] += 1 + counts[self.col_oracle] += 1 + counts[self.entry_oracle] += 1 + if self.is_controlled: + counts[CSwap(self.system_bitsize)] += 1 + counts[CSwap(1)] += 1 + else: + counts[Swap(self.system_bitsize)] += 1 + counts[Swap(1)] += 1 + counts[self.entry_oracle.adjoint()] += 1 + counts[self.col_oracle.adjoint()] += 1 + counts[self.diffusion.adjoint()] += 1 + + return set(counts.items()) + + def build_composite_bloq( + self, bb: BloqBuilder, system: SoquetT, ancilla: SoquetT, **soqs + ) -> dict[str, SoquetT]: + if is_symbolic(self.system_bitsize) or is_symbolic(self.col_oracle.num_nonzero): + raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") + + ctrl = soqs.pop('ctrl', None) + + assert not isinstance(ancilla, np.ndarray) + partition_ancilla = Partition( + n=self.ancilla_bitsize, + regs=( + Register('a', QBit()), + Register('l', QAny(self.system_bitsize)), + Register('b', QBit()), + ), + ) + + a, l, b = bb.add(partition_ancilla, x=ancilla) + + l = bb.add(self.diffusion, target=l) + l, system = bb.add(self.col_oracle, l=l, i=system) + b, l, system = bb.add(self.entry_oracle, q=b, i=l, j=system) + + if self.is_controlled: + ctrl, l, system = bb.add(CSwap(self.system_bitsize), ctrl=ctrl, x=l, y=system) + ctrl, a, b = bb.add(CSwap(1), ctrl=ctrl, x=a, y=b) + else: + l, system = bb.add(Swap(self.system_bitsize), x=l, y=system) + a, b = bb.add(Swap(1), x=a, y=b) + + b, l, system = bb.add(self.entry_oracle.adjoint(), q=b, i=l, j=system) + l, system = bb.add(self.col_oracle.adjoint(), l=l, i=system) + l = bb.add(self.diffusion.adjoint(), target=l) + + ancilla = bb.add(partition_ancilla.adjoint(), a=a, l=l, b=b) + + out_soqs = {"system": system, "ancilla": ancilla} + if self.is_controlled: + out_soqs |= {"ctrl": ctrl} + return out_soqs + + def adjoint(self) -> 'SparseMatrixHermitian': + return self + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + self, + ctrl_spec, + current_ctrl_bit=1 if self.is_controlled else None, + bloq_with_ctrl=self if self.is_controlled else attrs.evolve(self, is_controlled=True), + ctrl_reg_name='ctrl', + ) + + +@frozen +class UniformSqrtEntryOracle(SqrtEntryOracle): + """Oracle specifying the entries of a matrix with uniform entries.""" + + system_bitsize: SymbolicInt + entry: float + eps: float = 1e-11 + + @property + def epsilon(self) -> SymbolicFloat: + return self.eps + + def build_composite_bloq( + self, bb: BloqBuilder, q: Soquet, **soqs: SoquetT + ) -> dict[str, SoquetT]: + # Either Rx or Ry work here; Rx would induce a phase on the subspace with non-zero ancilla + # See https://arxiv.org/abs/2302.10949 for reference that uses Rx + soqs["q"] = bb.add(Ry(2 * np.arccos(np.sqrt(self.entry))), q=q) + return soqs + + +@bloq_example +def _sparse_matrix_hermitian_block_encoding() -> SparseMatrixHermitian: + from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle + from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle + + col_oracle = TopLeftRowColumnOracle(system_bitsize=2) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=2, entry=0.3) + sparse_matrix_hermitian_block_encoding = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0) + return sparse_matrix_hermitian_block_encoding + + +@bloq_example +def _sparse_matrix_symb_hermitian_block_encoding() -> SparseMatrixHermitian: + from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle + from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import UniformSqrtEntryOracle + + n = sympy.Symbol('n', positive=True, integer=True) + col_oracle = TopLeftRowColumnOracle(system_bitsize=n) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=n, entry=0.3) + sparse_matrix_symb_hermitian_block_encoding = SparseMatrixHermitian( + col_oracle, entry_oracle, eps=0 + ) + return sparse_matrix_symb_hermitian_block_encoding + + +_SPARSE_MATRIX_HERMITIAN_DOC = BloqDocSpec( + bloq_cls=SparseMatrixHermitian, + examples=[ + _sparse_matrix_symb_hermitian_block_encoding, + _sparse_matrix_hermitian_block_encoding, + ], +) diff --git a/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py new file mode 100644 index 000000000..367de6a3a --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py @@ -0,0 +1,118 @@ +# 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 +import sympy + +import qualtran.testing as qlt_testing +from qualtran import BloqBuilder, Soquet +from qualtran.bloqs.basic_gates import Hadamard, IntEffect, IntState +from qualtran.bloqs.block_encoding.sparse_matrix import TopLeftRowColumnOracle +from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import ( + _sparse_matrix_hermitian_block_encoding, + _sparse_matrix_symb_hermitian_block_encoding, + SparseMatrixHermitian, + UniformSqrtEntryOracle, +) +from qualtran.resource_counting.generalizers import ignore_split_join + + +def test_sparse_matrix(bloq_autotester): + bloq_autotester(_sparse_matrix_hermitian_block_encoding) + + +def test_sparse_matrix_symb(bloq_autotester): + bloq_autotester(_sparse_matrix_symb_hermitian_block_encoding) + + +def test_sparse_matrix_params(): + bloq = _sparse_matrix_hermitian_block_encoding() + assert bloq.system_bitsize == 2 + assert bloq.alpha == 4 + assert bloq.epsilon == 0 + assert bloq.ancilla_bitsize == 2 + 2 + assert bloq.resource_bitsize == 0 + + bloq = _sparse_matrix_symb_hermitian_block_encoding() + n = sympy.Symbol('n', positive=True, integer=True) + assert bloq.system_bitsize == n + assert bloq.alpha == 2**n + assert bloq.epsilon == 0 + assert bloq.ancilla_bitsize == n + 2 + assert bloq.resource_bitsize == 0 + + +def test_call_graph(): + bloq = _sparse_matrix_hermitian_block_encoding() + _, sigma = bloq.call_graph(generalizer=ignore_split_join) + assert sigma[Hadamard()] == 4 + + bloq = _sparse_matrix_symb_hermitian_block_encoding() + _, sigma = bloq.call_graph(generalizer=ignore_split_join) + n = sympy.Symbol('n', integer=True, positive=True) + assert sigma[Hadamard()] == 6 * n + + +def test_sparse_matrix_tensors(): + bloq = _sparse_matrix_hermitian_block_encoding() + alpha = bloq.alpha + bb = BloqBuilder() + system = bb.add_register("system", 2) + ancilla = cast(Soquet, bb.add(IntState(0, 4))) + system, ancilla = bb.add_t(bloq, system=system, ancilla=ancilla) + bb.add(IntEffect(0, 4), 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) + + +topleft_matrix = [ + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.3, 0.3, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], +] + + +def test_top_left_matrix(): + col_oracle = TopLeftRowColumnOracle(system_bitsize=3, num_nonzero=3) + entry_oracle = UniformSqrtEntryOracle(system_bitsize=3, entry=0.3) + bloq = SparseMatrixHermitian(col_oracle, entry_oracle, eps=0) + alpha = bloq.alpha + + bb = BloqBuilder() + system = bb.add_register("system", 3) + ancilla = cast(Soquet, bb.add(IntState(0, 3 + 2))) + system, ancilla = bb.add_t(bloq, system=system, ancilla=ancilla) + bb.add(IntEffect(0, 3 + 2), val=ancilla) + bloq = bb.finalize(system=system) + + from_tensors = bloq.tensor_contract() * alpha + np.testing.assert_allclose(topleft_matrix, from_tensors, atol=0.003) + + +def test_counts(): + qlt_testing.assert_equivalent_bloq_counts( + _sparse_matrix_hermitian_block_encoding(), generalizer=ignore_split_join + ) + qlt_testing.assert_equivalent_bloq_counts( + _sparse_matrix_hermitian_block_encoding().controlled(), generalizer=ignore_split_join + ) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index e229f0a2a..ede967863 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -116,6 +116,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'state_prep_alias_symb', # cannot serialize Shaped 'sparse_matrix_block_encoding', 'sparse_matrix_symb_block_encoding', + 'sparse_matrix_hermitian_block_encoding', + 'sparse_matrix_symb_hermitian_block_encoding', 'sparse_state_prep_alias_symb', # cannot serialize Shaped 'sparse_permutation', # contains nested tuple of inhomogeneous shape 'permutation_cycle_symb', # cannot serialize Shaped