From b18e8f803b493b29a6aa72fe59c720925009bccf Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Mon, 30 Sep 2024 17:13:53 +0200 Subject: [PATCH] planted noisy kXOR algorithm --- .../qualtran_dev_tools/notebook_specs.py | 56 + docs/bloqs/index.rst | 11 + qualtran/bloqs/arithmetic/sorting.py | 11 +- qualtran/bloqs/block_encoding/__init__.py | 1 + .../block_encoding/block_encoding_base.py | 8 + .../sparse_matrix_hermitian.ipynb | 203 ++ .../block_encoding/sparse_matrix_hermitian.py | 322 +++ .../sparse_matrix_hermitian_test.py | 115 + qualtran/bloqs/max_k_xor_sat/__init__.py | 19 + .../max_k_xor_sat/arithmetic/__init__.py | 16 + .../max_k_xor_sat/arithmetic/arithmetic.ipynb | 279 ++ .../arithmetic/has_duplicates.py | 69 + .../max_k_xor_sat/arithmetic/sort_in_place.py | 95 + .../arithmetic/symmetric_difference.py | 127 + .../arithmetic/symmetric_difference_test.py | 63 + .../guided_hamiltonian/__init__.py | 15 + .../guided_hamiltonian.ipynb | 311 ++ .../guided_hamiltonian/guided_hamiltonian.py | 348 +++ .../guided_hamiltonian_test.py | 31 + .../tutorial_guided_hamiltonian.ipynb | 2005 +++++++++++++ .../guided_hamiltonian/walk_operator.py | 95 + .../bloqs/max_k_xor_sat/guiding_state.ipynb | 381 +++ qualtran/bloqs/max_k_xor_sat/guiding_state.py | 410 +++ .../bloqs/max_k_xor_sat/guiding_state_test.py | 128 + .../max_k_xor_sat/kikuchi_adjacency_list.py | 346 +++ .../kikuchi_adjacency_list_test.py | 72 + .../max_k_xor_sat/kikuchi_adjacency_matrix.py | 190 ++ .../kikuchi_adjacency_matrix_test.py | 87 + .../kikuchi_block_encoding.ipynb | 1243 ++++++++ .../max_k_xor_sat/kikuchi_block_encoding.py | 251 ++ .../kikuchi_block_encoding_test.py | 59 + qualtran/bloqs/max_k_xor_sat/kxor_instance.py | 273 ++ .../bloqs/max_k_xor_sat/kxor_instance_test.py | 30 + .../max_k_xor_sat/load_kxor_instance.ipynb | 207 ++ .../bloqs/max_k_xor_sat/load_kxor_instance.py | 356 +++ .../max_k_xor_sat/load_kxor_instance_test.py | 49 + .../max_k_xor_sat/planted_noisy_kxor.ipynb | 235 ++ .../bloqs/max_k_xor_sat/planted_noisy_kxor.py | 424 +++ .../max_k_xor_sat/planted_noisy_kxor_test.py | 112 + .../bloqs/max_k_xor_sat/resource/__init__.py | 14 + .../max_k_xor_sat/resource/phase_gradient.py | 137 + .../resource/phase_gradient_test.py | 31 + qualtran/bloqs/max_k_xor_sat/shims.py | 148 + .../tutorial_guiding_state.ipynb | 2133 ++++++++++++++ .../tutorial_planted_noisy_kxor.py.ipynb | 2526 +++++++++++++++++ qualtran/bloqs/mcmt/and_bloq.py | 2 +- ...q_with_specialized_single_qubit_control.py | 87 + ...h_specialized_single_qubit_control_test.py | 110 + qualtran/bloqs/mcmt/multi_control_pauli.py | 8 +- .../phase_estimation/lp_resource_state.py | 5 +- .../phase_estimation/qpe_window_state.py | 7 +- .../state_preparation/black_box_prepare.py | 9 +- qualtran/conftest.py | 2 + qualtran/drawing/graphviz.py | 18 + qualtran/symbolics/simplification.py | 23 + 55 files changed, 14295 insertions(+), 18 deletions(-) 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 create mode 100644 qualtran/bloqs/max_k_xor_sat/__init__.py create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/__init__.py create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/arithmetic.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/has_duplicates.py create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/sort_in_place.py create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference.py create mode 100644 qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/__init__.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/tutorial_guided_hamiltonian.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/walk_operator.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guiding_state.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/guiding_state.py create mode 100644 qualtran/bloqs/max_k_xor_sat/guiding_state_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kxor_instance.py create mode 100644 qualtran/bloqs/max_k_xor_sat/kxor_instance_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/load_kxor_instance.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/load_kxor_instance.py create mode 100644 qualtran/bloqs/max_k_xor_sat/load_kxor_instance_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.py create mode 100644 qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/resource/__init__.py create mode 100644 qualtran/bloqs/max_k_xor_sat/resource/phase_gradient.py create mode 100644 qualtran/bloqs/max_k_xor_sat/resource/phase_gradient_test.py create mode 100644 qualtran/bloqs/max_k_xor_sat/shims.py create mode 100644 qualtran/bloqs/max_k_xor_sat/tutorial_guiding_state.ipynb create mode 100644 qualtran/bloqs/max_k_xor_sat/tutorial_planted_noisy_kxor.py.ipynb create mode 100644 qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control.py create mode 100644 qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control_test.py create mode 100644 qualtran/symbolics/simplification.py diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index 6dc00babe..aa9085939 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -90,6 +90,9 @@ import qualtran.bloqs.gf_arithmetic.gf2_multiplication import qualtran.bloqs.gf_arithmetic.gf2_square import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp +import qualtran.bloqs.max_k_xor_sat +import qualtran.bloqs.max_k_xor_sat.arithmetic +import qualtran.bloqs.max_k_xor_sat.guided_hamiltonian import qualtran.bloqs.mcmt.and_bloq import qualtran.bloqs.mcmt.controlled_via_and import qualtran.bloqs.mcmt.ctrl_spec_and @@ -865,6 +868,58 @@ ), ] +# -------------------------------------------------------------------------- +# ----- Quartic Speedups paper ------------------------------------------ +# -------------------------------------------------------------------------- +ALGO_QUARTIC_SPEEDUPS = [ + # ----- Preliminaries ------------------------------------------ + NotebookSpecV2( + title='Guided (sparse) Hamiltonian Problem', + module=qualtran.bloqs.max_k_xor_sat.guided_hamiltonian.guided_hamiltonian, + bloq_specs=[ + qualtran.bloqs.max_k_xor_sat.guided_hamiltonian.guided_hamiltonian._GUIDED_HAMILTONIAN_DOC, + qualtran.bloqs.max_k_xor_sat.guided_hamiltonian.guided_hamiltonian._GUIDED_HAMILTONIAN_PHASE_ESTIMATION_DOC, + ], + ), + NotebookSpecV2( + title='Arithmetic Primitives', + module=qualtran.bloqs.max_k_xor_sat.arithmetic, + bloq_specs=[ + qualtran.bloqs.max_k_xor_sat.arithmetic.sort_in_place._SORT_IN_PLACE_DOC, + qualtran.bloqs.max_k_xor_sat.arithmetic.symmetric_difference._SYMMETRIC_DIFFERENCE_DOC, + qualtran.bloqs.max_k_xor_sat.arithmetic.has_duplicates._HAS_DUPLICATES_DOC, + ], + ), + # ----- Algorithm ------------------------------------------ + NotebookSpecV2( + title='kXOR: Instance load Oracles', + module=qualtran.bloqs.max_k_xor_sat.load_kxor_instance, + bloq_specs=[qualtran.bloqs.max_k_xor_sat.load_kxor_instance._LOAD_INSTANCE_DOC], + ), + NotebookSpecV2( + title='Noisy kXOR: Guiding State', + module=qualtran.bloqs.max_k_xor_sat.guiding_state, + bloq_specs=[ + qualtran.bloqs.max_k_xor_sat.guiding_state._SIMPLE_GUIDING_STATE_DOC, + qualtran.bloqs.max_k_xor_sat.guiding_state._GUIDING_STATE_DOC, + ], + ), + NotebookSpecV2( + title='Noisy kXOR: Block-encoding the Kikuchi Matrix', + module=qualtran.bloqs.max_k_xor_sat.kikuchi_block_encoding, + bloq_specs=[ + qualtran.bloqs.max_k_xor_sat.kikuchi_adjacency_matrix._KIKUCHI_MATRIX_ENTRY_DOC, + qualtran.bloqs.max_k_xor_sat.kikuchi_adjacency_list._KIKUCHI_NONZERO_INDEX_DOC, + qualtran.bloqs.max_k_xor_sat.kikuchi_block_encoding._KIKUCHI_HAMILTONIAN_DOC, + ], + ), + NotebookSpecV2( + title='Algorithm: Planted Noise kXOR', + module=qualtran.bloqs.max_k_xor_sat.planted_noisy_kxor, + bloq_specs=[qualtran.bloqs.max_k_xor_sat.planted_noisy_kxor._PLANTED_NOISY_KXOR_DOC], + ), +] + NB_BY_SECTION = [ ('Basic Gates', BASIC_GATES), ('Chemistry', CHEMISTRY), @@ -873,5 +928,6 @@ ('GF Arithmetic', GF_ARITHMETIC), ('Rotations', ROT_QFT_PE), ('Block Encoding', BLOCK_ENCODING), + ('Paper: Quartic Quantum Speedups for Planted Inference', ALGO_QUARTIC_SPEEDUPS), ('Other', OTHER), ] diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 16c591baa..cae0a176d 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -132,6 +132,17 @@ Bloqs Library block_encoding/chebyshev_polynomial.ipynb block_encoding/lcu_block_encoding.ipynb +.. toctree:: + :maxdepth: 2 + :caption: Paper: Quartic Quantum Speedups for Planted Inference: + + max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.ipynb + max_k_xor_sat/arithmetic/arithmetic.ipynb + max_k_xor_sat/load_kxor_instance.ipynb + max_k_xor_sat/guiding_state.ipynb + max_k_xor_sat/kikuchi_block_encoding.ipynb + max_k_xor_sat/planted_noisy_kxor.ipynb + .. toctree:: :maxdepth: 2 :caption: Other: diff --git a/qualtran/bloqs/arithmetic/sorting.py b/qualtran/bloqs/arithmetic/sorting.py index d18db924e..c500effc9 100644 --- a/qualtran/bloqs/arithmetic/sorting.py +++ b/qualtran/bloqs/arithmetic/sorting.py @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeNotImplementedError, DecomposeTypeError, QBit, QUInt, @@ -222,8 +223,6 @@ def __attrs_post_init__(self): k = self.half_length if not is_symbolic(k): assert k >= 1, "length of input lists must be positive" - # TODO(#1090) support non-power-of-two input lengths - assert (k & (k - 1)) == 0, "length of input lists must be a power of 2" @cached_property def signature(self) -> 'Signature': @@ -249,14 +248,16 @@ def is_symbolic(self): def build_composite_bloq( self, bb: 'BloqBuilder', xs: 'SoquetT', ys: 'SoquetT' ) -> dict[str, 'SoquetT']: - if is_symbolic(self.half_length): + k = self.half_length + if is_symbolic(k): raise DecomposeTypeError(f"Cannot decompose symbolic {self=}") + if (k & (k - 1)) == 0: + # TODO(#1090) support non-power-of-two input lengths + raise DecomposeNotImplementedError("length of input lists must be a power of 2") assert isinstance(xs, np.ndarray) assert isinstance(ys, np.ndarray) - k = self.half_length - first_round_junk = [] for i in range(k): xs[i], ys[k - 1 - i], anc = bb.add(Comparator(self.bitsize), a=xs[i], b=ys[k - 1 - i]) 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/block_encoding_base.py b/qualtran/bloqs/block_encoding/block_encoding_base.py index 93e631529..76c9bdb51 100644 --- a/qualtran/bloqs/block_encoding/block_encoding_base.py +++ b/qualtran/bloqs/block_encoding/block_encoding_base.py @@ -84,6 +84,14 @@ def ancilla_bitsize(self) -> SymbolicInt: def resource_bitsize(self) -> SymbolicInt: """The number of resource qubits not counted in ancillas.""" + @property + def ctrl_bitsize(self) -> SymbolicInt: + """The number of control qubits, useful to define optimized custom controlled circuits. + + Usually either 0 or 1, as all other control cases can be reduced to 1. + """ + return 0 + @property @abc.abstractmethod def epsilon(self) -> SymbolicFloat: 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..c8bd6bf0a --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.ipynb @@ -0,0 +1,203 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "30c4ebd9", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Sparse Matrix (Hermitian)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5d9072c", + "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": "9cb4d637", + "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", + "\n", + "#### Registers\n", + " - `ctrl`: The single qubit control register. (present only if `cv` is not `None`)\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": "01cdcc22", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.block_encoding import SparseMatrixHermitian" + ] + }, + { + "cell_type": "markdown", + "id": "6bd99e38", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8337e6aa", + "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": "642141ad", + "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": "4f3434b5", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b875699", + "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": "a0918562", + "metadata": { + "cq.autogen": "SparseMatrixHermitian.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8128a66e", + "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..c67dcfd3f --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian.py @@ -0,0 +1,322 @@ +# 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 +from typing import Optional + +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. + + Registers: + ctrl: The single qubit control register. (present only if `cv` is not `None`) + 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 + cv: Optional[int] = None + + 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: + return Signature.build_from_dtypes( + ctrl=QAny(self.ctrl_bitsize), + 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[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 ctrl_bitsize(self) -> SymbolicInt: + return 1 if self.cv is not None else 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.ctrl_bitsize: + 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 ctrl: + 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) + + extra_soqs = {} + if ctrl: + extra_soqs = {'ctrl': ctrl} + + return {"system": system, "ancilla": ancilla} | extra_soqs + + def adjoint(self) -> 'SparseMatrixHermitian': + return self + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.bloq_with_specialized_single_qubit_control import ( + get_ctrl_system_for_bloq_with_specialized_single_qubit_control, + ) + + return get_ctrl_system_for_bloq_with_specialized_single_qubit_control(self, ctrl_spec) + + def with_cv(self, cv: Optional[int]) -> 'SparseMatrixHermitian': + return attrs.evolve(self, cv=cv) + + @property + def ctrl_reg_name(self) -> str: + return '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..4164d8178 --- /dev/null +++ b/qualtran/bloqs/block_encoding/sparse_matrix_hermitian_test.py @@ -0,0 +1,115 @@ +# 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 + ) diff --git a/qualtran/bloqs/max_k_xor_sat/__init__.py b/qualtran/bloqs/max_k_xor_sat/__init__.py new file mode 100644 index 000000000..e13ed5ffc --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/__init__.py @@ -0,0 +1,19 @@ +# 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 .guiding_state import GuidingState, SimpleGuidingState +from .kikuchi_adjacency_list import KikuchiNonZeroIndex +from .kikuchi_adjacency_matrix import KikuchiMatrixEntry +from .kikuchi_block_encoding import KikuchiHamiltonian, KikuchiMatrixEntry, KikuchiNonZeroIndex +from .kxor_instance import Constraint, KXorInstance +from .planted_noisy_kxor import AliceTheorem, PlantedNoisyKXOR diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/__init__.py b/qualtran/bloqs/max_k_xor_sat/arithmetic/__init__.py new file mode 100644 index 000000000..7ed84457f --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/__init__.py @@ -0,0 +1,16 @@ +# 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 .has_duplicates import HasDuplicates +from .sort_in_place import SortInPlace +from .symmetric_difference import SymmetricDifference diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/arithmetic.ipynb b/qualtran/bloqs/max_k_xor_sat/arithmetic/arithmetic.ipynb new file mode 100644 index 000000000..5e7082fd2 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/arithmetic.ipynb @@ -0,0 +1,279 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "acae7447", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Arithmetic Primitives" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba9f152f", + "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": "e9eee46c", + "metadata": { + "cq.autogen": "SortInPlace.bloq_doc.md" + }, + "source": [ + "## `SortInPlace`\n", + "Sort a list of $\\ell$ numbers in place using $\\ell \\log \\ell$ ancilla bits.\n", + "\n", + "Applies the map:\n", + "$$\n", + " |x_1, x_2, \\ldots, x_l\\rangle\n", + " |0^{\\ell \\log \\ell}\\rangle\n", + " \\mapsto\n", + " |x_{\\pi_1}, x_{\\pi_2}, \\ldots, x_{\\pi_\\ell})\\rangle\n", + " |\\pi_1, \\pi_2, \\ldots, \\pi_\\ell\\rangle\n", + "$$\n", + "where $x_{\\pi_1} \\le x_{\\pi_2} \\ldots \\le x_{\\pi_\\ell}$ is the sorted list,\n", + "and the ancilla are entangled.\n", + "\n", + "To apply this, we first use any sorting algorithm to output the sorted list\n", + "in a clean register. And then use the following algorithm from Lemma 4.12 of Ref [1]\n", + "that applies the map:\n", + "\n", + "$$\n", + " |x_1, ..., x_l\\rangle|x_{\\pi(1)}, ..., x_{\\pi(l)})\\rangle\n", + " \\mapsto\n", + " |x_l, ..., x_l\\rangle|\\pi(1), ..., \\pi(l))\\rangle\n", + "$$\n", + "\n", + "where $x_i \\in [n]$ and $\\pi(i) \\in [l]$.\n", + "This second algorithm (Lemma 4.12) has two steps, each with $l^2$ comparisons:\n", + "1. compute `pi(1) ... pi(l)` given `x_1 ... x_l` and `x_{pi(1)} ... x{pi(l)}`.\n", + "1. (un)compute `x_{pi(1)} ... x{pi(l)}` using `pi(1) ... pi(l)` given `x_1 ... x_l`.\n", + "\n", + "#### Parameters\n", + " - `l`: number of elements in the list\n", + " - `dtype`: type of each element to store `[n]`. \n", + "\n", + "#### Registers\n", + " - `input`: the entire input as a single register\n", + " - `ancilla`: the generated (entangled) register storing `pi`. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Lemma 4.12. Eq. 122.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7e360aa", + "metadata": { + "cq.autogen": "SortInPlace.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.arithmetic import SortInPlace" + ] + }, + { + "cell_type": "markdown", + "id": "49eb1524", + "metadata": { + "cq.autogen": "SymmetricDifference.bloq_doc.md" + }, + "source": [ + "## `SymmetricDifference`\n", + "Given two sorted sets $S, T$ of unique elements, compute their symmetric difference.\n", + "\n", + "This accepts an integer `n_diff`, and marks a flag qubit if the symmetric difference\n", + "set is of size exactly `n_diff`. If the flag is marked (1), then the output of `n_diff`\n", + "numbers is the symmetric difference, otherwise it may be arbitrary.\n", + "\n", + "#### Parameters\n", + " - `n_lhs`: number of elements in $S$\n", + " - `n_rhs`: number of elements in $T$\n", + " - `n_diff`: expected number of elements in the difference $S \\Delta T$.\n", + " - `bitsize`: number of bits of each element. \n", + "\n", + "#### Registers\n", + " - `S`: list of `n_lhs` numbers.\n", + " - `T`: list of `n_rhs` numbers.\n", + " - `diff`: output register of `n_diff` numbers.\n", + " - `flag`: 1 if there are duplicates, 0 if all are unique. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Theorem 4.17, proof para 3, page 38.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d90b223", + "metadata": { + "cq.autogen": "SymmetricDifference.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.arithmetic import SymmetricDifference" + ] + }, + { + "cell_type": "markdown", + "id": "98c40f78", + "metadata": { + "cq.autogen": "SymmetricDifference.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0255609", + "metadata": { + "cq.autogen": "SymmetricDifference.symm_diff" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.symbolics import bit_length\n", + "\n", + "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", + "symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, bitsize=bit_length(n - 1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a66c7fe", + "metadata": { + "cq.autogen": "SymmetricDifference.symm_diff_equal_size" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.symbolics import bit_length\n", + "\n", + "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", + "symm_diff_equal_size = SymmetricDifference(\n", + " n_lhs=c * k, n_rhs=c * k, n_diff=k, bitsize=bit_length(n - 1)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ffabfb9e", + "metadata": { + "cq.autogen": "SymmetricDifference.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5236e7e1", + "metadata": { + "cq.autogen": "SymmetricDifference.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([symm_diff, symm_diff_equal_size],\n", + " ['`symm_diff`', '`symm_diff_equal_size`'])" + ] + }, + { + "cell_type": "markdown", + "id": "9eab4ef5", + "metadata": { + "cq.autogen": "SymmetricDifference.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01b8dc0c", + "metadata": { + "cq.autogen": "SymmetricDifference.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "symm_diff_g, symm_diff_sigma = symm_diff.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(symm_diff_g)\n", + "show_counts_sigma(symm_diff_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "3a6ec6d0", + "metadata": { + "cq.autogen": "HasDuplicates.bloq_doc.md" + }, + "source": [ + "## `HasDuplicates`\n", + "Given a sorted list of `l` numbers, check if it contains any duplicates.\n", + "\n", + "Produces a single qubit which is `1` if there are duplicates, and `0` if all are disjoint.\n", + "It compares every adjacent pair, and therefore uses `l - 1` comparisons.\n", + "It then uses a single MCX on `l - 1` bits gate to compute the flag.\n", + "\n", + "#### Parameters\n", + " - `l`: number of elements in the list\n", + " - `dtype`: type of each element to store `[n]`. \n", + "\n", + "#### Registers\n", + " - `input`: the entire input as a single register\n", + " - `flag`: 1 if there are duplicates, 0 if all are unique. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Lemma 4.12. Eq. 122.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58923ab1", + "metadata": { + "cq.autogen": "HasDuplicates.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.arithmetic import HasDuplicates" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/has_duplicates.py b/qualtran/bloqs/max_k_xor_sat/arithmetic/has_duplicates.py new file mode 100644 index 000000000..9e576db23 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/has_duplicates.py @@ -0,0 +1,69 @@ +# 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 collections import Counter + +from attrs import frozen + +from qualtran import Bloq, BloqDocSpec, QAny, QBit, QDType, Signature +from qualtran.bloqs.arithmetic import LessThanEqual +from qualtran.bloqs.mcmt import MultiControlX +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import HasLength, SymbolicInt + + +@frozen +class HasDuplicates(Bloq): + r"""Given a sorted list of `l` numbers, check if it contains any duplicates. + + Produces a single qubit which is `1` if there are duplicates, and `0` if all are disjoint. + It compares every adjacent pair, and therefore uses `l - 1` comparisons. + It then uses a single MCX on `l - 1` bits gate to compute the flag. + + Args: + l: number of elements in the list + dtype: type of each element to store `[n]`. + + Registers: + input: the entire input as a single register + flag (RIGHT): 1 if there are duplicates, 0 if all are unique. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Lemma 4.12. Eq. 122. + """ + + l: SymbolicInt + dtype: QDType + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(input=QAny(self.input_bitsize), flag=QBit()) + + @property + def input_bitsize(self) -> SymbolicInt: + return self.l * self.dtype.num_qubits + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + logn = self.dtype.num_qubits + + counts = Counter[Bloq]() + + counts[LessThanEqual(logn, logn)] += self.l - 1 + counts[MultiControlX(cvs=HasLength(self.l - 1))] += 1 + counts[LessThanEqual(logn, logn).adjoint()] += self.l - 1 + + return counts + + +_HAS_DUPLICATES_DOC = BloqDocSpec(bloq_cls=HasDuplicates, examples=[]) diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/sort_in_place.py b/qualtran/bloqs/max_k_xor_sat/arithmetic/sort_in_place.py new file mode 100644 index 000000000..2cb9fb3c4 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/sort_in_place.py @@ -0,0 +1,95 @@ +# 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 collections import Counter + +from attrs import frozen + +from qualtran import Bloq, BloqDocSpec, QAny, QDType, Signature +from qualtran.bloqs.arithmetic import Xor +from qualtran.bloqs.arithmetic.sorting import Comparator +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ceil, log2, SymbolicInt + + +@frozen +class SortInPlace(Bloq): + r"""Sort a list of $\ell$ numbers in place using $\ell \log \ell$ ancilla bits. + + Applies the map: + $$ + |x_1, x_2, \ldots, x_l\rangle + |0^{\ell \log \ell}\rangle + \mapsto + |x_{\pi_1}, x_{\pi_2}, \ldots, x_{\pi_\ell})\rangle + |\pi_1, \pi_2, \ldots, \pi_\ell\rangle + $$ + where $x_{\pi_1} \le x_{\pi_2} \ldots \le x_{\pi_\ell}$ is the sorted list, + and the ancilla are entangled. + + To apply this, we first use any sorting algorithm to output the sorted list + in a clean register. And then use the following algorithm from Lemma 4.12 of Ref [1] + that applies the map: + + $$ + |x_1, ..., x_l\rangle|x_{\pi(1)}, ..., x_{\pi(l)})\rangle + \mapsto + |x_l, ..., x_l\rangle|\pi(1), ..., \pi(l))\rangle + $$ + + where $x_i \in [n]$ and $\pi(i) \in [l]$. + This second algorithm (Lemma 4.12) has two steps, each with $l^2$ comparisons: + 1. compute `pi(1) ... pi(l)` given `x_1 ... x_l` and `x_{pi(1)} ... x{pi(l)}`. + 1. (un)compute `x_{pi(1)} ... x{pi(l)}` using `pi(1) ... pi(l)` given `x_1 ... x_l`. + + Args: + l: number of elements in the list + dtype: type of each element to store `[n]`. + + Registers: + input: the entire input as a single register + ancilla (RIGHT): the generated (entangled) register storing `pi`. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Lemma 4.12. Eq. 122. + """ + + l: SymbolicInt + dtype: QDType + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + input=QAny(self.l * self.dtype.num_qubits), ancilla=QAny(self.ancilla_bitsize) + ) + + @property + def ancilla_bitsize(self) -> SymbolicInt: + """total number of entangled ancilla qubits generated""" + return self.l * ceil(log2(self.l)) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + compare = Comparator(self.dtype.num_qubits) + n_ops = 3 * self.l**2 + + counts = Counter[Bloq]() + + counts[compare] += n_ops + counts[compare.adjoint()] += n_ops + counts[Xor(self.dtype)] += n_ops + + return counts + + +_SORT_IN_PLACE_DOC = BloqDocSpec(bloq_cls=SortInPlace, examples=[]) diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference.py b/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference.py new file mode 100644 index 000000000..8f24fb333 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference.py @@ -0,0 +1,127 @@ +# 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 collections import Counter + +from attrs import frozen + +from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, QBit, Signature +from qualtran.bloqs.arithmetic import Equals, EqualsAConstant, HammingWeightCompute +from qualtran.bloqs.arithmetic.sorting import BitonicMerge +from qualtran.bloqs.basic_gates import CNOT +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import bit_length, is_symbolic, SymbolicInt + + +@frozen +class SymmetricDifference(Bloq): + r"""Given two sorted sets $S, T$ of unique elements, compute their symmetric difference. + + This accepts an integer `n_diff`, and marks a flag qubit if the symmetric difference + set is of size exactly `n_diff`. If the flag is marked (1), then the output of `n_diff` + numbers is the symmetric difference, otherwise it may be arbitrary. + + Args: + n_lhs: number of elements in $S$ + n_rhs: number of elements in $T$ + n_diff: expected number of elements in the difference $S \Delta T$. + bitsize: number of bits of each element. + + Registers: + S: list of `n_lhs` numbers. + T: list of `n_rhs` numbers. + diff: output register of `n_diff` numbers. + flag: 1 if there are duplicates, 0 if all are unique. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Theorem 4.17, proof para 3, page 38. + """ + + n_lhs: SymbolicInt + n_rhs: SymbolicInt + n_diff: SymbolicInt + bitsize: SymbolicInt + + def __attrs_post_init__(self): + if not is_symbolic(self.n_lhs, self.n_rhs): + assert self.n_lhs >= self.n_rhs, "lhs must be the larger set" + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + S=QAny(self.n_lhs * self.bitsize), + T=QAny(self.n_rhs * self.bitsize), + diff=QAny(self.n_diff * self.bitsize), + flag=QBit(), + ) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + # the forward pass, i.e. all bloqs that must be uncomputed + counts_forward = Counter[Bloq]() + + # merge the lists + counts_forward[BitonicMerge(self.n_lhs, self.bitsize)] += 1 + # compare adjacents + counts_forward[Equals(QAny(self.bitsize))] += self.n_lhs + self.n_rhs - 1 + # compute number of equal adjacents + counts_forward[HammingWeightCompute(self.n_lhs + self.n_rhs - 1)] += 1 + # check: 2 * n_equal = n_lhs + n_rhs - n_diff + # (note: the above eq holds as we assume all input elements are unique) + counts_forward[ + EqualsAConstant( + bit_length(self.n_lhs + self.n_rhs - 1), + (self.n_lhs + self.n_rhs - self.n_diff) // 2, + ) + ] += 1 + + # all bloqs + counts = Counter[Bloq]() + + # copy the first n_diff numbers and flag + counts[CNOT()] += self.n_diff * self.bitsize + 1 + + for bloq, n in counts_forward.items(): + counts[bloq] += n + counts[bloq.adjoint()] += n + + return counts + + +@bloq_example +def _symm_diff() -> SymmetricDifference: + import sympy + + from qualtran.symbolics import bit_length + + n, k, c = sympy.symbols("n k c", positive=True, integer=True) + symm_diff = SymmetricDifference(n_lhs=c * k, n_rhs=k, n_diff=c * k, bitsize=bit_length(n - 1)) + return symm_diff + + +@bloq_example +def _symm_diff_equal_size() -> SymmetricDifference: + import sympy + + from qualtran.symbolics import bit_length + + n, k, c = sympy.symbols("n k c", positive=True, integer=True) + symm_diff_equal_size = SymmetricDifference( + n_lhs=c * k, n_rhs=c * k, n_diff=k, bitsize=bit_length(n - 1) + ) + return symm_diff_equal_size + + +_SYMMETRIC_DIFFERENCE_DOC = BloqDocSpec( + bloq_cls=SymmetricDifference, examples=[_symm_diff, _symm_diff_equal_size] +) diff --git a/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference_test.py b/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference_test.py new file mode 100644 index 000000000..76a320f95 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/arithmetic/symmetric_difference_test.py @@ -0,0 +1,63 @@ +# 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 unittest.mock import ANY + +import pytest + +from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost +from qualtran.symbolics import ceil, log2 + +from .symmetric_difference import _symm_diff, _symm_diff_equal_size + + +@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +@pytest.mark.parametrize("bloq_ex", [_symm_diff, _symm_diff_equal_size]) +def test_cost(bloq_ex): + bloq = bloq_ex() + gc = get_cost_value(bloq, QECGatesCost()) + + l, r = bloq.n_lhs, bloq.n_rhs # assumption l >= r + logn = bloq.bitsize + logl = ceil(log2(l)) + assert gc == GateCounts( + cswap=2 * l * logn * (logl + 1), + and_bloq=( + 2 * l * (2 * logn + 1) * (logl + 1) + + l + + r + + 2 * ((logn - 1) * (l + r - 1)) + + 2 * ceil(log2(l + r)) + - 4 + ), + clifford=ANY, + measurement=ANY, + ) + + # \tilde{O}(l log n) + # Page 38, Thm 4.17, proof para 3, 3rd last line. + assert gc.total_t_count() in big_O(l * logn * logl**2) + + +@pytest.mark.notebook +def test_notebook(): + from qualtran.testing import execute_notebook + + execute_notebook('arithmetic') diff --git a/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/__init__.py b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/__init__.py new file mode 100644 index 000000000..09dfda120 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/__init__.py @@ -0,0 +1,15 @@ +# 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 .guided_hamiltonian import GuidedHamiltonian, GuidedHamiltonianPhaseEstimation +from .walk_operator import QubitizedWalkOperator diff --git a/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.ipynb b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.ipynb new file mode 100644 index 000000000..c9d611d64 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.ipynb @@ -0,0 +1,311 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e299e7a9", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Guided (sparse) Hamiltonian Problem\n", + "\n", + "Section 4.4.2 Simulating the Kikuchi Hamiltonian\n", + "\n", + "This module contains oracles to implement the block-encoding of the Kikuchi\n", + "Hamiltonian corresponding to an input k-XOR-SAT instance.\n", + "\n", + "References:\n", + " [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1)\n", + " Section 4.4.2 for algorithm. Section 2.4 for definitions and notation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2be674c", + "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": "8d10248d", + "metadata": { + "cq.autogen": "GuidedHamiltonian.bloq_doc.md" + }, + "source": [ + "## `GuidedHamiltonian`\n", + "Solve the guided (sparse) hamiltonian problem.\n", + "\n", + "Definition 4.8 (modified to accept any block-encoding):\n", + "In the Guided Hamiltonian problem we are given the following as input:\n", + "\n", + "1. A $(\\sqrt{2} s, \\cdot, 0)$-block-encoding of a Hamiltonian $H$ such that $\\|H\\|_\\max \\le s$.\n", + "2. A unitary program that prepares $|\\Psi\\rangle|0^A\\rangle$.\n", + "3. Parameters $\\lambda \\in [-\\Lambda, \\Lambda]$, $\\alpha \\in (0, 1)$, $\\gamma \\in (0, 1]$.\n", + "\n", + "and we should output\n", + "\n", + "- YES (1) if $\\| \\Pi_{\\ge \\lambda} (H) |\\Psi\\rangle \\| \\ge \\gamma$\n", + "- NO (0) if $\\|H\\| \\le (1 - \\alpha) \\lambda$\n", + "\n", + "Note that the above drops the sparse requirement, and accepts any\n", + "$(\\alpha_H, \\cdot, \\cdot)$-block-encoding of $H$.\n", + "In the sparse Hamiltonian case, $\\alpha_H = s$ (where $s$ is the sparsity).\n", + "\n", + "Algorithm (Theorem 4.9):\n", + " This uses phase estimation on the block-encoding of $e^{iHt}$, and then uses\n", + " amplitude amplification to increase the success probability to $1 - o(1)$.\n", + "\n", + "We instead directly do phase-estimation on the qubitized (Szegedy) walk operator for $H$\n", + "\n", + "#### Parameters\n", + " - `hamiltonian`: the block-encoding of $H$\n", + " - `guiding_state`: the unitary that prepares $|\\Psi\\rangle$\n", + " - `lambd`: parameter $\\lambda$\n", + " - `alpha`: parameter $\\alpha$\n", + " - `gamma`: parameter $\\gamma$ \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Section 4.2 \"Guided Sparse Hamiltonian Problem\".\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29d536c3", + "metadata": { + "cq.autogen": "GuidedHamiltonian.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.guided_hamiltonian import GuidedHamiltonian" + ] + }, + { + "cell_type": "markdown", + "id": "a216ac70", + "metadata": { + "cq.autogen": "GuidedHamiltonian.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fabf68e4", + "metadata": { + "cq.autogen": "GuidedHamiltonian.guided_hamiltonian" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat import GuidingState, KikuchiHamiltonian, KXorInstance\n", + "from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare\n", + "from qualtran.symbolics import ceil, log2\n", + "\n", + "n, k, m, c = sympy.symbols(\"n k m c\", positive=True, integer=True)\n", + "zeta = sympy.symbols(r\"\\zeta\", positive=True)\n", + "\n", + "inst_guide = KXorInstance.symbolic(n, (1 - zeta) * m, k, max_rhs=2)\n", + "inst_solve = KXorInstance.symbolic(n, zeta * m, k, max_rhs=2)\n", + "l = c * k\n", + "s = l * ceil(log2(n))\n", + "\n", + "Psi = GuidingState(inst_guide, l)\n", + "H = KikuchiHamiltonian(inst_solve, c * k, s)\n", + "\n", + "lambd, alpha, gamma = sympy.symbols(r\"\\lambda \\alpha \\gamma\", positive=True, real=True)\n", + "guided_hamiltonian = GuidedHamiltonian(H, BlackBoxPrepare(Psi), lambd, alpha, gamma)" + ] + }, + { + "cell_type": "markdown", + "id": "7ad9d994", + "metadata": { + "cq.autogen": "GuidedHamiltonian.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ae17ad5", + "metadata": { + "cq.autogen": "GuidedHamiltonian.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([guided_hamiltonian],\n", + " ['`guided_hamiltonian`'])" + ] + }, + { + "cell_type": "markdown", + "id": "1b3e1663", + "metadata": { + "cq.autogen": "GuidedHamiltonian.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08946e48", + "metadata": { + "cq.autogen": "GuidedHamiltonian.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "guided_hamiltonian_g, guided_hamiltonian_sigma = guided_hamiltonian.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(guided_hamiltonian_g)\n", + "show_counts_sigma(guided_hamiltonian_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "0b22b193", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.bloq_doc.md" + }, + "source": [ + "## `GuidedHamiltonianPhaseEstimation`\n", + "Implement the phase estimation algorithm $U_\\text{PE}$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3806eb0", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.guided_hamiltonian import GuidedHamiltonianPhaseEstimation" + ] + }, + { + "cell_type": "markdown", + "id": "500f891d", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfdbf9a6", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.guided_phase_estimate_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat import GuidingState, KikuchiHamiltonian, KXorInstance\n", + "from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare\n", + "from qualtran.symbolics import ceil, log2\n", + "\n", + "n, k, c = sympy.symbols(\"n k c\", positive=True, integer=True)\n", + "m_guide, m_solve = sympy.symbols(\"m_1 m_2\", positive=True, integer=True)\n", + "\n", + "inst_guide = KXorInstance.symbolic(n, m_guide, k, max_rhs=2)\n", + "inst_solve = KXorInstance.symbolic(n, m_solve, k, max_rhs=2)\n", + "l = c * k\n", + "s = l * ceil(log2(n))\n", + "\n", + "Psi = GuidingState(inst_guide, l)\n", + "H = KikuchiHamiltonian(inst_solve, c * k, s)\n", + "\n", + "eps, delta = sympy.symbols(r\"\\epsilon_{PE} \\delta_{PE}\", positive=True, real=True)\n", + "guided_phase_estimate_symb = GuidedHamiltonianPhaseEstimation(\n", + " H, BlackBoxPrepare(Psi), eps, delta\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "d6408318", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a1d74b2", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([guided_phase_estimate_symb],\n", + " ['`guided_phase_estimate_symb`'])" + ] + }, + { + "cell_type": "markdown", + "id": "a5d710cd", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "749b6440", + "metadata": { + "cq.autogen": "GuidedHamiltonianPhaseEstimation.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "guided_phase_estimate_symb_g, guided_phase_estimate_symb_sigma = guided_phase_estimate_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(guided_phase_estimate_symb_g)\n", + "show_counts_sigma(guided_phase_estimate_symb_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/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.py b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.py new file mode 100644 index 000000000..1c9553b68 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian.py @@ -0,0 +1,348 @@ +# 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. +# +# 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. +"""Section 4.4.2 Simulating the Kikuchi Hamiltonian + +This module contains oracles to implement the block-encoding of the Kikuchi +Hamiltonian corresponding to an input k-XOR-SAT instance. + +References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.2 for algorithm. Section 2.4 for definitions and notation. +""" +from collections import Counter +from functools import cached_property + +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QAny, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import ZGate +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.mcmt import MultiControlZ +from qualtran.bloqs.phase_estimation import KaiserWindowState, QubitizationQPE +from qualtran.bloqs.phase_estimation.qpe_window_state import QPEWindowStateBase +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ( + ceil, + HasLength, + is_symbolic, + is_zero, + ln, + log2, + pi, + SymbolicFloat, + SymbolicInt, +) + +from ..shims import ReflectAboutZero +from .walk_operator import QubitizedWalkOperator + + +@frozen +class GuidedHamiltonianPhaseEstimation(Bloq): + r"""Implement the phase estimation algorithm $U_\text{PE}$""" + + hamiltonian: BlockEncoding + guiding_state: BlackBoxPrepare + precision: SymbolicFloat + fail_prob: SymbolicFloat + + def __attrs_post_init__(self): + assert ( + self.hamiltonian.resource_bitsize == 0 + ), "block encoding with resource regs not supported" + + assert self.hamiltonian.system_bitsize == self.guiding_state.selection_bitsize + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + phase_estimate=self.qpe_window_state.m_register.dtype, + system=QAny(self.hamiltonian.system_bitsize), + walk_ancilla=QAny(self.hamiltonian.ancilla_bitsize), + guide_ancilla=QAny(self.guiding_state.junk_bitsize), + ) + + @cached_property + def walk_operator(self) -> QubitizedWalkOperator: + return QubitizedWalkOperator(self.hamiltonian) + + @cached_property + def qpe_window_state(self) -> QPEWindowStateBase: + """Kaiser Window state. + Computes a slightly larger value for a simpler expression. + https://arxiv.org/abs/2209.13581, Eq D14, D15 + """ + eps, delta = self.precision, self.fail_prob + + alpha = ln(1 / delta) / pi(delta) + + N = (1 / eps) * ln(1 / delta) + m_bits = ceil(log2(N)) + return KaiserWindowState(bitsize=m_bits, alpha=alpha) + + @cached_property + def qpe_bloq(self) -> QubitizationQPE: + return QubitizationQPE(self.walk_operator, self.qpe_window_state) # type: ignore + + def build_composite_bloq( + self, + bb: 'BloqBuilder', + phase_estimate: Soquet, + system: Soquet, + walk_ancilla: Soquet, + **soqs: SoquetT, + ) -> dict[str, 'SoquetT']: + + # prepare the guiding state + if is_zero(self.guiding_state.junk_bitsize): + system = bb.add(self.guiding_state, selection=system) + else: + system, guide_ancilla = bb.add( + self.guiding_state, selection=system, junk=soqs.pop('guide_ancilla') + ) + soqs['guide_ancilla'] = guide_ancilla + + # apply QPE + phase_estimate, system, walk_ancilla = bb.add( + self.qpe_bloq, qpe_reg=phase_estimate, system=system, ancilla=walk_ancilla + ) + + return { + 'phase_estimate': phase_estimate, + 'system': system, + 'walk_ancilla': walk_ancilla, + } | soqs + + +@bloq_example +def _guided_phase_estimate_symb() -> GuidedHamiltonianPhaseEstimation: + import sympy + + from qualtran.bloqs.max_k_xor_sat import GuidingState, KikuchiHamiltonian, KXorInstance + from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare + from qualtran.symbolics import ceil, log2 + + n, k, c = sympy.symbols("n k c", positive=True, integer=True) + m_guide, m_solve = sympy.symbols("m_1 m_2", positive=True, integer=True) + + inst_guide = KXorInstance.symbolic(n, m_guide, k, max_rhs=2) + inst_solve = KXorInstance.symbolic(n, m_solve, k, max_rhs=2) + l = c * k + s = l * ceil(log2(n)) + + Psi = GuidingState(inst_guide, l) + H = KikuchiHamiltonian(inst_solve, c * k, s) + + eps, delta = sympy.symbols(r"\epsilon_{PE} \delta_{PE}", positive=True, real=True) + guided_phase_estimate_symb = GuidedHamiltonianPhaseEstimation( + H, BlackBoxPrepare(Psi), eps, delta + ) + + return guided_phase_estimate_symb + + +_GUIDED_HAMILTONIAN_PHASE_ESTIMATION_DOC = BloqDocSpec( + bloq_cls=GuidedHamiltonianPhaseEstimation, examples=[_guided_phase_estimate_symb] +) + + +@frozen +class GuidedHamiltonian(Bloq): + r"""Solve the guided (sparse) hamiltonian problem. + + Definition 4.8 (modified to accept any block-encoding): + In the Guided Hamiltonian problem we are given the following as input: + + 1. A $(\sqrt{2} s, \cdot, 0)$-block-encoding of a Hamiltonian $H$ such that $\|H\|_\max \le s$. + 2. A unitary program that prepares $|\Psi\rangle|0^A\rangle$. + 3. Parameters $\lambda \in [-\Lambda, \Lambda]$, $\alpha \in (0, 1)$, $\gamma \in (0, 1]$. + + and we should output + + - YES (1) if $\| \Pi_{\ge \lambda} (H) |\Psi\rangle \| \ge \gamma$ + - NO (0) if $\|H\| \le (1 - \alpha) \lambda$ + + Note that the above drops the sparse requirement, and accepts any + $(\alpha_H, \cdot, \cdot)$-block-encoding of $H$. + In the sparse Hamiltonian case, $\alpha_H = s$ (where $s$ is the sparsity). + + Algorithm (Theorem 4.9): + This uses phase estimation on the block-encoding of $e^{iHt}$, and then uses + amplitude amplification to increase the success probability to $1 - o(1)$. + + We instead directly do phase-estimation on the qubitized (Szegedy) walk operator for $H$ + + Args: + hamiltonian: the block-encoding of $H$ + guiding_state: the unitary that prepares $|\Psi\rangle$ + lambd: parameter $\lambda$ + alpha: parameter $\alpha$ + gamma: parameter $\gamma$ + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.2 "Guided Sparse Hamiltonian Problem". + """ + + hamiltonian: BlockEncoding + guiding_state: BlackBoxPrepare + lambd: SymbolicFloat + alpha: SymbolicFloat + gamma: SymbolicFloat + + def __attrs_post_init__(self): + assert self.hamiltonian.resource_bitsize == 0, "resource not supported" + assert ( + self.hamiltonian.system_bitsize == self.guiding_state.selection_bitsize + ), "system registers must match" + + @property + def signature(self) -> 'Signature': + return self.qpe_bloq.signature + + @cached_property + def qpe_precision(self) -> SymbolicFloat: + r"""The precision for phase estimation. + + Page 31, Eq 100 of the reference gives the precision value for estimating phases + of $e^{iHt}$ with $t = \pi/(2s)$. But this bloq does phase estimation directly + on the walk operator, with eigenphases $e^{-i \arccos(H/s)}$. + + To bound this, consider the two eigenvalues that are to be distinguished: + $\lambda$ and $(1 - \alpha)\lambda$. We can bound the difference in estimated phases as + + $$ + |\arccos(\lambda / s) - \arccos((1-\alpha)\lambda / s)| + \le \frac{\alpha \lambda}{s} \frac{1}{1 - ((1 - \alpha)\lambda / s)^2} + $$ + + As we know $\|H\| \le s/\sqrt{2}$, it means $\lambda/s \le 1/\sqrt{2}$, + therefore the second term is at most $\sqrt{2}$. + + In the sparse encoding case, we can increase the sparsity to $\sqrt{2} s$ + when block-encoding the input, to ensure that we have an epsilon bound of + $\alpha \lambda / s$. + """ + return self.alpha * self.lambd / self.hamiltonian.alpha + + @cached_property + def qpe_fail_prob(self) -> SymbolicFloat: + """Page 31, above Eq 104.""" + return self.gamma**3 + + @cached_property + def n_rounds_amplification(self) -> SymbolicInt: + return ceil(1 / self.gamma) + + @cached_property + def qpe_bloq(self) -> GuidedHamiltonianPhaseEstimation: + return GuidedHamiltonianPhaseEstimation( + hamiltonian=self.hamiltonian, + guiding_state=self.guiding_state, + precision=self.qpe_precision, + fail_prob=self.qpe_fail_prob, + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + if is_symbolic(self.n_rounds_amplification): + raise DecomposeTypeError( + f'cannot decompose {self} with symbolic number of rounds {self.n_rounds_amplification}' + ) + + soqs = bb.add_d(self.qpe_bloq, **soqs) + for _ in range(self.n_rounds_amplification): + # reflect about bad state + soqs['guide_ancilla'] = bb.add( + ReflectAboutZero( + (Register('guide_ancilla', QAny(self.guiding_state.junk_bitsize)),), + global_phase=-1, + ), + guide_ancilla=soqs['guide_ancilla'], + ) + + # reflect about prepared state + soqs = bb.add_d(self.qpe_bloq.adjoint(), **soqs) + soqs = bb.add_d(ReflectAboutZero(tuple(self.signature), global_phase=-1), **soqs) + soqs = bb.add_d(self.qpe_bloq, **soqs) + + return soqs + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + counts = Counter[Bloq]() + + # prepare the initial state + counts[self.qpe_bloq] += 1 + + # reflect about the prepared state + counts[self.qpe_bloq.adjoint()] += self.n_rounds_amplification + counts[ + MultiControlZ(HasLength(self.qpe_bloq.signature.n_qubits())) + ] += self.n_rounds_amplification + counts[self.qpe_bloq] += self.n_rounds_amplification + + # reflect about the flag qubit + counts[ZGate()] += self.n_rounds_amplification + + return counts + + +@bloq_example +def _guided_hamiltonian() -> GuidedHamiltonian: + import sympy + + from qualtran.bloqs.max_k_xor_sat import GuidingState, KikuchiHamiltonian, KXorInstance + from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare + from qualtran.symbolics import ceil, log2 + + n, k, m, c = sympy.symbols("n k m c", positive=True, integer=True) + zeta = sympy.symbols(r"\zeta", positive=True) + + inst_guide = KXorInstance.symbolic(n, (1 - zeta) * m, k, max_rhs=2) + inst_solve = KXorInstance.symbolic(n, zeta * m, k, max_rhs=2) + l = c * k + s = l * ceil(log2(n)) + + Psi = GuidingState(inst_guide, l) + H = KikuchiHamiltonian(inst_solve, c * k, s) + + lambd, alpha, gamma = sympy.symbols(r"\lambda \alpha \gamma", positive=True, real=True) + guided_hamiltonian = GuidedHamiltonian(H, BlackBoxPrepare(Psi), lambd, alpha, gamma) + return guided_hamiltonian + + +_GUIDED_HAMILTONIAN_DOC = BloqDocSpec(bloq_cls=GuidedHamiltonian, examples=[_guided_hamiltonian]) diff --git a/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian_test.py b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian_test.py new file mode 100644 index 000000000..1ab6c5465 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/guided_hamiltonian_test.py @@ -0,0 +1,31 @@ +# 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 pytest + +from .guided_hamiltonian import _guided_hamiltonian, _guided_phase_estimate_symb + + +@pytest.mark.parametrize("bloq_ex", [_guided_hamiltonian, _guided_phase_estimate_symb]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +@pytest.mark.notebook +def test_notebook(): + from qualtran.testing import execute_notebook + + execute_notebook('guided_hamiltonian') diff --git a/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/tutorial_guided_hamiltonian.ipynb b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/tutorial_guided_hamiltonian.ipynb new file mode 100644 index 000000000..fe31ae38e --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/tutorial_guided_hamiltonian.ipynb @@ -0,0 +1,2005 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6d27900d7109a493", + "metadata": {}, + "source": [ + "# Guided (sparse) Hamiltonian Problem" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ec7403af5f8f9302", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-27T18:07:47.146430Z", + "start_time": "2024-08-27T18:07:44.824865Z" + } + }, + "outputs": [], + "source": [ + "from attrs import frozen\n", + "import attrs\n", + "from qualtran import Bloq, Signature, Register, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from qualtran.symbolics import SymbolicInt, ceil, log2, ln, is_symbolic\n", + "from qualtran.resource_counting import big_O\n", + "from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join, ignore_cliffords\n", + "from qualtran.bloqs.block_encoding import BlockEncoding\n", + "from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare\n", + "from qualtran.bloqs.max_k_xor_sat.guided_hamiltonian import GuidedHamiltonian\n", + "from qualtran.bloqs.max_k_xor_sat.shims import ArbitraryGate # arbitrary 1/2-qubit gate, for costing." + ] + }, + { + "cell_type": "markdown", + "id": "fd4d302a-8cfe-48b5-8e44-a9b99a0b5e5b", + "metadata": {}, + "source": [ + "## `GuidedHamiltonian`\n", + "Solve the guided (sparse) hamiltonian problem.\n", + "\n", + "Definition 4.8 (modified with sparsity generalized to any):\n", + "In the Guided Hamiltonian problem we are given the following as input:\n", + "\n", + "1. A Hamiltonian $H$ with $\\|H\\|_\\max \\le 1$, specified via a block-encoding.\n", + "2. A unitary program that takes $|0^N\\rangle|0^A\\rangle$ and prepares $|\\Psi\\rangle|0^A\\rangle$.\n", + "3. Parameters $\\lambda \\in [-\\Lambda, \\Lambda]$, $\\alpha \\in (0, 1)$, $\\gamma \\in (0, 1]$.\n", + "\n", + "and we should output\n", + "\n", + "- YES if $\\| \\Pi_{\\ge \\lambda} (H) |\\Psi\\rangle \\| \\ge \\gamma$\n", + "- NO if $\\|H\\| \\le (1 - \\alpha) \\lambda$\n", + "\n", + "Note that the above drops the sparse requirement, and accepts any\n", + "$(\\alpha_H, \\cdot, \\cdot)$-block-encoding of $H$.\n", + "In the sparse Hamiltonian case, $\\alpha_H = s$ (where $s$ is the sparsity).\n", + "\n", + "Algorithm (Theorem 4.9):\n", + " This uses phase estimation on the block-encoding of $e^{iHt}$, and then uses\n", + " amplitude amplification to increase the success probability to $1 - o(1)$.\n", + "\n", + "We instead directly do phase-estimation on the qubitized (Szegedy) walk operator for $H$.\n", + "\n", + "#### Parameters\n", + " - `hamiltonian`: the block-encoding of $H$\n", + " - `guiding_state`: the unitary that prepares $|\\Psi\\rangle$\n", + " - `lambd`: parameter $\\lambda$\n", + " - `alpha`: parameter $\\alpha$\n", + " - `gamma`: parameter $\\gamma$ " + ] + }, + { + "cell_type": "markdown", + "id": "06cdbc3b-b4bd-47da-84ba-cd7b0059bb6f", + "metadata": {}, + "source": [ + "# Computing Query Costs with Qualtran\n", + "\n", + "We will first create black-boxes for a Hamiltonian and a guiding state preparation, that can be passed to the `GuidedHamiltonian` bloq to count number of queries and gates." + ] + }, + { + "cell_type": "markdown", + "id": "fb7db14a-d5d1-4f02-b082-43ada6fa1aee", + "metadata": {}, + "source": [ + "## Building black-box oracles" + ] + }, + { + "cell_type": "markdown", + "id": "b50f74cc-e8dc-4214-a020-af0e80ef5469", + "metadata": {}, + "source": [ + "### Graph oracles for the Hamiltonian\n", + "We first build the oracles $O_F$ and $O_H$, and use them to block-encode a sparse matrix." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4a490bf8-e83f-497a-a422-2e1eb2444e70", + "metadata": {}, + "outputs": [], + "source": [ + "@frozen\n", + "class oracle_O_H(Bloq):\n", + " \"\"\"given (i, j), output H_{i, j}\"\"\"\n", + " N: SymbolicInt\n", + " entry_bits: SymbolicInt\n", + " \n", + " @property\n", + " def signature(self):\n", + " return Signature.build(i=self.N, j=self.N, entry=self.entry_bits)\n", + "\n", + " def adjoint(self):\n", + " return self\n", + "\n", + "@frozen\n", + "class oracle_O_F(Bloq):\n", + " \"\"\"Given (i, k), output (i, f(i, k)) s.t. f(i, k) is the k-th non zero entry in row i\"\"\"\n", + " N: SymbolicInt\n", + " reverse: bool = False\n", + " \n", + " @property\n", + " def signature(self):\n", + " return Signature.build(i=self.N, k=self.N)\n", + "\n", + " def adjoint(self):\n", + " return oracle_O_F(self.N, reverse=not self.reverse)\n", + "\n", + "\n", + "@frozen\n", + "class EncodeSparseHamiltonian(BlockEncoding):\n", + " \"\"\"(s, N+1, 0)-block-encoding of s-sparse NxN matrix H\"\"\"\n", + " N: SymbolicInt\n", + " s: SymbolicInt # sparsity\n", + " O_F: oracle_O_F\n", + " O_H: oracle_O_H\n", + "\n", + " @property\n", + " def signature(self) -> Signature:\n", + " return Signature.build_from_dtypes(\n", + " system=QAny(self.system_bitsize),\n", + " ancilla=QAny(self.ancilla_bitsize),\n", + " )\n", + "\n", + " @property\n", + " def system_bitsize(self):\n", + " return self.N\n", + "\n", + " @property\n", + " def ancilla_bitsize(self):\n", + " return self.N + 1\n", + "\n", + " @property\n", + " def resource_bitsize(self):\n", + " return 0\n", + "\n", + " @property\n", + " def alpha(self):\n", + " return self.s\n", + "\n", + " @property\n", + " def epsilon(self):\n", + " return 0\n", + "\n", + " @property\n", + " def signal_state(self):\n", + " from qualtran.bloqs.reflections.prepare_identity import PrepareIdentity\n", + " from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare\n", + " \n", + " return BlackBoxPrepare(PrepareIdentity.from_bitsizes([self.ancilla_bitsize]))\n", + "\n", + " def build_call_graph(self, ssa):\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", + " \"\"\"\n", + " log_s = ceil(log2(self.s))\n", + " return {(self.O_F, 2), (self.O_H, 2), (ArbitraryGate(), 2*log_s)}" + ] + }, + { + "cell_type": "markdown", + "id": "60df1af0-0a6f-4645-8739-b75f4ece3553", + "metadata": {}, + "source": [ + "### State-preparation Oracle for the guiding state\n", + "\n", + "Point 2. Quantum circuit that uses $G$ gates and maps $|0^N\\rangle|0^A\\rangle$ to $|\\Psi\\rangle|0^A\\rangle$" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0a17eab5-0a44-4526-9b08-483086c655bf", + "metadata": {}, + "outputs": [], + "source": [ + "@frozen\n", + "class GuidingState(Bloq):\n", + " \"\"\"Point 2. Quantum circuit that uses G gates and maps |0^N>|0^A> to |\\Psi>|0^A>\"\"\"\n", + " N: SymbolicInt\n", + " A: SymbolicInt\n", + " G: SymbolicInt\n", + " \n", + " @property\n", + " def signature(self) -> Bloq:\n", + " return Signature.build(selection=self.N, junk=self.A)\n", + "\n", + " def build_call_graph(self, ssa):\n", + " return {(ArbitraryGate(), self.G)}\n", + " \n", + " @property\n", + " def selection_bitsize(self):\n", + " return self.N\n", + " @property\n", + " def junk_bitsize(self):\n", + " return self.A\n", + " @property\n", + " def selection_registers(self):\n", + " return (Register('selection', QAny(self.N)),)\n", + " @property\n", + " def junk_registers(self):\n", + " return (Register('junk', QAny(self.A)),)" + ] + }, + { + "cell_type": "markdown", + "id": "f127675c-6616-43eb-b33a-479ae146dcfa", + "metadata": {}, + "source": [ + "## An example invocation\n", + "With these oracles in place, we can now invoke the `GuidedHamiltonian` algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "32219420-f41b-4c80-b89b-2076795d596f", + "metadata": {}, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "def example_bloq() -> GuidedHamiltonian:\n", + " N, A, G, s = sympy.symbols(\"N A G s\", positive=True, integer=True)\n", + " lambd, alpha, gamma = sympy.symbols(r\"\\lambda \\alpha \\gamma\", positive=True, real=True)\n", + "\n", + " O_F = oracle_O_F(N)\n", + " O_H = oracle_O_H(N, 10)\n", + " be_H = EncodeSparseHamiltonian(N, s, O_F, O_H)\n", + " psi = GuidingState(N, A, G)\n", + "\n", + " return GuidedHamiltonian(be_H, psi, lambd, alpha, gamma)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "13b8eb29-1af9-4d65-a954-cf96498efad1", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "phase_estimate_G9\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "GuidedHamiltonian\n", + "\n", + "GuidedHamiltonian\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "phase_estimate_G9:e->GuidedHamiltonian:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G11\n", + "system\n", + "\n", + "\n", + "\n", + "system_G11:e->GuidedHamiltonian:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G0\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "walk_ancilla_G0:e->GuidedHamiltonian:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G8\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "guide_ancilla_G8:e->GuidedHamiltonian:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "phase_estimate_G2\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "GuidedHamiltonian:e->phase_estimate_G2:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G7\n", + "system\n", + "\n", + "\n", + "\n", + "GuidedHamiltonian:e->system_G7:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G3\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonian:e->walk_ancilla_G3:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G4\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonian:e->guide_ancilla_G4:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bloq = example_bloq()\n", + "show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "ac277b8a-8ae9-47cc-bd46-7f23c56d1392", + "metadata": {}, + "source": [ + "## Circuit Diagrams\n", + "\n", + "We will now look at the decomposition of the bloq, to see the steps of the algorithm.\n", + "First we look at the phase estimation bloq $U_\\text{PE}$.\n", + "To obtain the entire algorithm, we use amplitude-amplification on $U_\\text{PE}$ for $O(1/\\gamma)$ rounds.\n", + "The good subspace is characterized by `guide_ancilla` being all 0." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "9d2c34d3-9a19-4d54-a60a-549c590533e7", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "phase_estimate_G8\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "QubitizationQPE\n", + "\n", + "QubitizationQPE\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "phase_estimate_G8:e->QubitizationQPE:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G9\n", + "system\n", + "\n", + "\n", + "\n", + "GuidingState\n", + "\n", + "GuidingState\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "system_G9:e->GuidingState:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "walk_ancilla:e->QubitizationQPE:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G7\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "guide_ancilla_G7:e->GuidingState:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidingState:e->QubitizationQPE:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "guide_ancilla\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidingState:e->guide_ancilla:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "phase_estimate\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->phase_estimate:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G3\n", + "system\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->system_G3:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G1\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->walk_ancilla_G1:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_bloq(bloq.qpe_bloq.decompose_bloq())" + ] + }, + { + "cell_type": "markdown", + "id": "08002b28-5d5f-4283-add6-d4fd914a0112", + "metadata": {}, + "source": [ + "### Full Circuit\n", + "For exposition, let us pick the number of rounds as a constant (say, $3$) to see the decomposition:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "46e5a6af-4270-49fd-af5a-ae066503e378", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "phase_estimate_G52\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G18\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "phase_estimate_G52:e->GuidedHamiltonianPhaseEstimation_G18:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G97\n", + "system\n", + "\n", + "\n", + "\n", + "system_G97:e->GuidedHamiltonianPhaseEstimation_G18:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G88\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "walk_ancilla_G88:e->GuidedHamiltonianPhaseEstimation_G18:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G74\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "guide_ancilla_G74:e->GuidedHamiltonianPhaseEstimation_G18:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint\n", + "\n", + "Adjoint(subbloq=GuidedHamiltonianPhaseEstimation)\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "ReflectAboutZero\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation:e->Adjoint:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation:e->Adjoint:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation:e->Adjoint:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G23\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation:e->ReflectAboutZero_G23:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G4\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G9\n", + "\n", + "Adjoint(subbloq=GuidedHamiltonianPhaseEstimation)\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G4:e->Adjoint_G9:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G4:e->Adjoint_G9:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G4:e->Adjoint_G9:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G25\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G4:e->ReflectAboutZero_G25:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G32\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G9:e->ReflectAboutZero_G32:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint_G9:e->ReflectAboutZero_G32:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G9:e->ReflectAboutZero_G32:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "Adjoint_G9:e->ReflectAboutZero_G32:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G37\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "ReflectAboutZero:e->GuidedHamiltonianPhaseEstimation_G37:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero:e->GuidedHamiltonianPhaseEstimation_G37:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero:e->GuidedHamiltonianPhaseEstimation_G37:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero:e->GuidedHamiltonianPhaseEstimation_G37:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint_G27\n", + "\n", + "Adjoint(subbloq=GuidedHamiltonianPhaseEstimation)\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G18:e->Adjoint_G27:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G18:e->Adjoint_G27:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G18:e->Adjoint_G27:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G42\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G18:e->ReflectAboutZero_G42:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G23:e->Adjoint:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G25:e->Adjoint_G9:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G27:e->ReflectAboutZero_G44:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint_G27:e->ReflectAboutZero_G44:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G27:e->ReflectAboutZero_G44:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "Adjoint_G27:e->ReflectAboutZero_G44:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G32:e->GuidedHamiltonianPhaseEstimation:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G32:e->GuidedHamiltonianPhaseEstimation:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G32:e->GuidedHamiltonianPhaseEstimation:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G32:e->GuidedHamiltonianPhaseEstimation:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "phase_estimate_G75\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G37:e->phase_estimate_G75:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G54\n", + "system\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G37:e->system_G54:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G51\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G37:e->walk_ancilla_G51:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G81\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidedHamiltonianPhaseEstimation_G37:e->guide_ancilla_G81:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G42:e->Adjoint_G27:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44:e->GuidedHamiltonianPhaseEstimation_G4:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44:e->GuidedHamiltonianPhaseEstimation_G4:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44:e->GuidedHamiltonianPhaseEstimation_G4:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44:e->GuidedHamiltonianPhaseEstimation_G4:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bloq_4_rounds = attrs.evolve(bloq, gamma=1/3)\n", + "show_bloq(bloq_4_rounds.decompose_bloq())" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "214f731f-22d8-4767-8b23-13ad1c400abf", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "phase_estimate_G62\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G40\n", + "\n", + "QubitizationQPE\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "phase_estimate_G62:e->QubitizationQPE_G40:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G112\n", + "system\n", + "\n", + "\n", + "\n", + "GuidingState\n", + "\n", + "GuidingState\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "system_G112:e->GuidingState:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G102\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "walk_ancilla_G102:e->QubitizationQPE_G40:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "guide_ancilla_G78\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "guide_ancilla_G78:e->GuidingState:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidingState:e->QubitizationQPE_G40:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidingState:e->ReflectAboutZero_G44:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint\n", + "\n", + "Adjoint(subbloq=QubitizationQPE)\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G19\n", + "\n", + "Adjoint(subbloq=GuidingState)\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "Adjoint:e->Adjoint_G19:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G49\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero_G49:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint:e->ReflectAboutZero_G49:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "GuidingState_G0\n", + "\n", + "GuidingState\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G54\n", + "\n", + "QubitizationQPE\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "GuidingState_G0:e->QubitizationQPE_G54:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "guide_ancilla_G88\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "GuidingState_G0:e->guide_ancilla_G88:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G46\n", + "\n", + "Adjoint(subbloq=GuidingState)\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "ReflectAboutZero:e->Adjoint_G46:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint_G3\n", + "\n", + "Adjoint(subbloq=GuidingState)\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G22\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G3:e->ReflectAboutZero_G22:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G3:e->ReflectAboutZero_G22:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G6\n", + "\n", + "ReflectAboutZero\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G27\n", + "\n", + "QubitizationQPE\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G6:e->QubitizationQPE_G27:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G6:e->QubitizationQPE_G27:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "GuidingState_G33\n", + "\n", + "GuidingState\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G6:e->GuidingState_G33:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G6:e->GuidingState_G33:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "QubitizationQPE\n", + "\n", + "QubitizationQPE\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Adjoint_G15\n", + "\n", + "Adjoint(subbloq=QubitizationQPE)\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->Adjoint_G15:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->Adjoint_G15:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "QubitizationQPE:e->Adjoint_G15:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "GuidingState_G12\n", + "\n", + "GuidingState\n", + "\n", + "selection\n", + "\n", + "junk\n", + "\n", + "\n", + "\n", + "GuidingState_G12:e->ReflectAboutZero:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidingState_G12:e->QubitizationQPE:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G15:e->ReflectAboutZero_G6:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint_G15:e->ReflectAboutZero_G6:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "Adjoint_G15:e->Adjoint_G46:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G19:e->ReflectAboutZero_G49:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G19:e->ReflectAboutZero_G49:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G22:e->GuidingState_G0:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G22:e->GuidingState_G0:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G22:e->QubitizationQPE_G54:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G22:e->QubitizationQPE_G54:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "Adjoint_G36\n", + "\n", + "Adjoint(subbloq=QubitizationQPE)\n", + "\n", + "qpe_reg\n", + "\n", + "system\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G27:e->Adjoint_G36:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G27:e->Adjoint_G36:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G27:e->Adjoint_G36:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G31\n", + "\n", + "ReflectAboutZero\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G31:e->Adjoint_G3:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "GuidingState_G33:e->QubitizationQPE_G27:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "GuidingState_G33:e->ReflectAboutZero_G31:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint_G36:e->Adjoint_G3:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G36:e->ReflectAboutZero_G22:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "Adjoint_G36:e->ReflectAboutZero_G22:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G40:e->Adjoint:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G40:e->Adjoint:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G40:e->Adjoint:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G44:e->Adjoint_G19:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "Adjoint_G46:e->ReflectAboutZero_G6:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "Adjoint_G46:e->ReflectAboutZero_G6:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G49:e->QubitizationQPE:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G49:e->QubitizationQPE:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G49:e->GuidingState_G12:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "ReflectAboutZero_G49:e->GuidingState_G12:w\n", + "\n", + "\n", + "A\n", + "\n", + "\n", + "\n", + "phase_estimate_G79\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G54:e->phase_estimate_G79:w\n", + "\n", + "\n", + "ceiling(lo ...\n", + "\n", + "\n", + "\n", + "system_G65\n", + "system\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G54:e->system_G65:w\n", + "\n", + "\n", + "N\n", + "\n", + "\n", + "\n", + "walk_ancilla_G61\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "QubitizationQPE_G54:e->walk_ancilla_G61:w\n", + "\n", + "\n", + "N + 1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_bloq(bloq_4_rounds.decompose_bloq().flatten_once())" + ] + }, + { + "cell_type": "markdown", + "id": "f131e5ff-4d5b-487b-a84c-56c68a411f71", + "metadata": {}, + "source": [ + "## Query and gate costs\n", + "We will count queries to the above oracles, and arbitrary 1/2-qubit gates as described in the paper." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "cf13f9e4-c65a-41f6-94ef-70168a136191", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.shims import generalize_1_2_qubit_gates\n", + "\n", + "g, sigma = bloq.call_graph(\n", + " generalizer=[ignore_alloc_free, ignore_split_join, generalize_1_2_qubit_gates]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "18fee717-4117-47ce-8929-1aa3e7fba91a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "#### Counts totals:\n", + " - `And`: $\\displaystyle \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + N \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) + N \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2\\right) + N - 4\\right) + \\left(A + 2 N + \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + N \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) + N \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2\\right) + N - 4\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `And†`: $\\displaystyle \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + N \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) + N \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2\\right) + N - 4\\right) + \\left(A + 2 N + \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + N \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) + N \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2\\right) + N - 4\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `ArbitraryGate`: $\\displaystyle 2 \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil$\n", + " - `ArbitraryGate`: $\\displaystyle \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + G + \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + 3\\right) + \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor + 13 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + 2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\left\\lfloor{2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}}\\right\\rfloor \\right)}}\\right\\rceil + \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor - 16\\right) + \\left(4 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + G + \\left(2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 2\\right) \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + 3\\right) + \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor + 13 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + 2 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\left\\lfloor{2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}}\\right\\rfloor \\right)}}\\right\\rceil + \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor - 16\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `C[oracle_O_F]`: $\\displaystyle 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 2$\n", + " - `C[oracle_O_F]`: $\\displaystyle 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `C[oracle_O_H]`: $\\displaystyle 4 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 2$\n", + " - `PrepareIdentity`: $\\displaystyle \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + 4 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 6\\right) + \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} + 4 \\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 6\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `oracle_O_F`: $\\displaystyle \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 4\\right) \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right)$\n", + " - `oracle_O_F`: $\\displaystyle \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 4\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `oracle_O_H`: $\\displaystyle \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 4\\right) \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) + \\left(2 \\cdot 2^{\\left\\lceil{\\operatorname{log}_{2}{\\left(\\frac{s \\log{\\left(\\frac{1}{\\gamma^{3}} \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil} - 4\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_counts_sigma(sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "a6083b59-ed3f-4543-ba28-d7f7b19d2b5a", + "metadata": {}, + "source": [ + "### Cost from the paper\n", + "Theorem 4.9 of the paper states that the algorithm uses:\n", + "1. $Q = \\widetilde{O}(s / (\\gamma \\alpha \\lambda))$ queries to oracles for H\n", + "2. $\\widetilde{O}(G/\\gamma + \\text{polylog}(Q)/\\gamma + QN)$ gates\n", + "\n", + "Let us simplify the symbolic costs obtained above and verify if they match." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "9e03abf4-efd3-46b0-83d7-bf3b1b0554a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "#### Counts totals:\n", + " - `And`: $\\displaystyle \\frac{\\alpha \\lambda \\left(A + 2 N + \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(- 2 N \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) + \\alpha \\lambda \\left(2 N \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) + N - 4\\right) - 24 s \\log{\\left(\\gamma \\right)}\\right) + \\left(- 2 N \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) + \\alpha \\lambda \\left(2 N \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) + N - 4\\right) - 24 s \\log{\\left(\\gamma \\right)}\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil}{\\alpha \\lambda}$\n", + " - `And†`: $\\displaystyle \\frac{\\alpha \\lambda \\left(A + 2 N + \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(- 2 N \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) + \\alpha \\lambda \\left(2 N \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) + N - 4\\right) - 24 s \\log{\\left(\\gamma \\right)}\\right) + \\left(- 2 N \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) + \\alpha \\lambda \\left(2 N \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) + N - 4\\right) - 24 s \\log{\\left(\\gamma \\right)}\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil}{\\alpha \\lambda}$\n", + " - `ArbitraryGate`: $\\displaystyle 2 \\cdot \\left(2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil$\n", + " - `ArbitraryGate`: $\\displaystyle \\frac{2 \\alpha \\lambda \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right) \\left(\\alpha \\lambda \\left(G + \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor + 15 \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor - 16\\right) - 24 s \\log{\\left(\\gamma \\right)} \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2 \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + 3\\right)\\right) + \\left(\\alpha \\lambda \\left(G + \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 1\\right) \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor + 15 \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil + \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor - 16\\right) - 24 s \\log{\\left(\\gamma \\right)} \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 2 \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + 3\\right)\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil}{\\alpha \\lambda}$\n", + " - `C[oracle_O_F]`: $\\displaystyle 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 2$\n", + " - `C[oracle_O_F]`: $\\displaystyle 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil$\n", + " - `C[oracle_O_H]`: $\\displaystyle 4 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 2$\n", + " - `PrepareIdentity`: $\\displaystyle \\frac{2 \\left(\\alpha \\lambda \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 3\\right) - 6 s \\log{\\left(\\gamma \\right)}\\right) \\left(2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right)}{\\alpha \\lambda}$\n", + " - `oracle_O_F`: $\\displaystyle - \\frac{4 \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) \\left(\\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil + 1\\right)}{\\alpha \\lambda}$\n", + " - `oracle_O_F`: $\\displaystyle \\frac{4 \\left(- \\alpha \\lambda - 3 s \\log{\\left(\\gamma \\right)}\\right) \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil}{\\alpha \\lambda}$\n", + " - `oracle_O_H`: $\\displaystyle \\frac{4 \\left(\\alpha \\lambda + 3 s \\log{\\left(\\gamma \\right)}\\right) \\left(- 2 \\left\\lceil{\\frac{1}{\\gamma}}\\right\\rceil - 1\\right)}{\\alpha \\lambda}$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def simplify_2_pow_log(my_expr, inner_term):\n", + " # replace `2**(ceil(log2(T)))` upper bound `2T`.\n", + " temp = sympy.symbols(f\"_temp\", positive=True, integer=True)\n", + " my_expr = my_expr.replace(ceil(log2(inner_term)), temp)\n", + " my_expr = my_expr.replace(2**temp, 2 * inner_term)\n", + " my_expr = my_expr.replace(temp, ceil(log2(inner_term)))\n", + " return my_expr\n", + "\n", + "def simplify_expression(expr):\n", + " if not is_symbolic(expr): return expr\n", + " N, A, G, s = sympy.symbols(\"N A G s\", positive=True, integer=True)\n", + " lambd, alpha, gamma = sympy.symbols(r\"\\lambda \\alpha \\gamma\", positive=True, real=True)\n", + "\n", + " expr = simplify_2_pow_log(expr, (s * ln(1/gamma**3)) / (alpha * lambd))\n", + " expr = sympy.simplify(expr)\n", + " return expr\n", + "\n", + "sigma_simpl = {k: simplify_expression(v) for k, v in sigma.items()}\n", + "show_counts_sigma(sigma_simpl)" + ] + }, + { + "cell_type": "markdown", + "id": "2676ae19-41b6-49a9-a16f-706e74b19cf9", + "metadata": {}, + "source": [ + "### Cost of Phase Estimation\n", + "\n", + "For simplicity, we can also look at the cost of a single phase estimation call (which is repeated $1/\\gamma$ times to obtain the above algorithm)." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "355eab2b-e136-4ff1-b131-a63248bbee31", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "#### Counts totals:\n", + " - `And`: $\\displaystyle 2 N \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\log{\\left(\\gamma^{\\frac{3 s}{\\alpha \\lambda}} \\right)} \\right)}}\\right\\rceil - 3 N - 4 - \\frac{\\log{\\left(\\gamma^{24 s} \\gamma^{6 N s} \\right)}}{\\alpha \\lambda}$\n", + " - `And†`: $\\displaystyle 2 N \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\log{\\left(\\gamma^{\\frac{3 s}{\\alpha \\lambda}} \\right)} \\right)}}\\right\\rceil - 3 N - 4 - \\frac{\\log{\\left(\\gamma^{24 s} \\gamma^{6 N s} \\right)}}{\\alpha \\lambda}$\n", + " - `ArbitraryGate`: $\\displaystyle 2 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil$\n", + " - `ArbitraryGate`: $\\displaystyle G - 4 \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil + \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil \\left\\lfloor{\\frac{\\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{2}}\\right\\rfloor + 15 \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 22 - \\frac{12 s \\log{\\left(\\gamma \\right)} \\left\\lceil{\\operatorname{log}_{2}{\\left(s \\right)}}\\right\\rceil}{\\alpha \\lambda} - \\frac{24 s \\log{\\left(\\gamma \\right)} \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil}{\\alpha \\lambda} - \\frac{18 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda}$\n", + " - `C[oracle_O_F]`: 2\n", + " - `C[oracle_O_H]`: 2\n", + " - `PrepareIdentity`: $\\displaystyle 4 \\left\\lceil{\\operatorname{log}_{2}{\\left(- \\frac{3 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda} \\right)}}\\right\\rceil - 6 - \\frac{12 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda}$\n", + " - `oracle_O_F`: $\\displaystyle -4 - \\frac{12 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda}$\n", + " - `oracle_O_H`: $\\displaystyle -4 - \\frac{12 s \\log{\\left(\\gamma \\right)}}{\\alpha \\lambda}$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g_pe, sigma_pe = bloq.qpe_bloq.call_graph(\n", + " generalizer=[ignore_alloc_free, ignore_split_join, generalize_1_2_qubit_gates]\n", + ")\n", + "sigma_pe = {k: simplify_expression(v) for k, v in sigma_pe.items()}\n", + "show_counts_sigma(sigma_pe)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cda3ad1-8183-4091-9faf-6c777a283951", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/walk_operator.py b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/walk_operator.py new file mode 100644 index 000000000..48cf3447e --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guided_hamiltonian/walk_operator.py @@ -0,0 +1,95 @@ +# 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. +# +# 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 functools import cached_property + +import attrs + +from qualtran import BloqBuilder, Signature, SoquetT +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import SymbolicFloat, SymbolicInt + + +@attrs.frozen +class QubitizedWalkOperator(BlockEncoding): + r"""Construct a Szegedy Quantum Walk operator of a block encoding. + + Args: + block_encoding: The input block-encoding. + + References: + [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). + Babbush et. al. (2018). Figure 1. + """ + + block_encoding: BlockEncoding + + @property + def alpha(self) -> SymbolicFloat: + return self.block_encoding.alpha + + @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 epsilon(self) -> SymbolicFloat: + return self.block_encoding.epsilon + + @property + def signal_state(self) -> BlackBoxPrepare: + return self.block_encoding.signal_state + + @cached_property + def signature(self) -> Signature: + return self.block_encoding.signature + + @cached_property + def reflect(self) -> ReflectionUsingPrepare: + return ReflectionUsingPrepare(self.block_encoding.signal_state, global_phase=-1) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + soqs |= bb.add_d(self.block_encoding, **soqs) + soqs |= bb.add_d( + self.reflect, **{reg.name: soqs[reg.name] for reg in self.reflect.signature} + ) + return soqs + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return {self.block_encoding: 1, self.reflect: 1} + + def __str__(self): + return f'Walk[{self.block_encoding}]' diff --git a/qualtran/bloqs/max_k_xor_sat/guiding_state.ipynb b/qualtran/bloqs/max_k_xor_sat/guiding_state.ipynb new file mode 100644 index 000000000..c593fce46 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guiding_state.ipynb @@ -0,0 +1,381 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "13ac92c7", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Noisy kXOR: Guiding State\n", + "\n", + "Prepare the guiding state for a kXOR instance $\\mathcal{I}$ with\n", + "Kikuchi parameter $\\ell$.\n", + "\n", + "References:\n", + " [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1)\n", + " Section 4.4.1, Theorem 4.15." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1aabf565", + "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": "96c16e6a", + "metadata": { + "cq.autogen": "SimpleGuidingState.bloq_doc.md" + }, + "source": [ + "## `SimpleGuidingState`\n", + "Prepare the guiding state for $\\ell = k$.\n", + "\n", + "Given an kXOR instance $\\mathcal{I}$, prepare the guiding state for\n", + "parameter $\\ell = k$ (i.e. $c = 1$), defined in Eq 134:\n", + " $$\n", + " |\\phi\\rangle\n", + " \\propto\n", + " |\\Gamma^k(\\mathcal{A})\\rangle\n", + " =\n", + " \\frac{1}{\\sqrt{\\tilde{m}}}\n", + " \\sum_{S \\in {[n] \\choose k}} B_\\mathcal{I}(S) |S\\rangle\n", + " $$\n", + "\n", + "Here, $\\tilde{m}$ is the number of constraints in the input instance $\\mathcal{I}$,\n", + "and $\\mathcal{A} = \\sqrt{\\frac{{n\\choose k}}{\\tilde{m}}} \\mathcal{I}$.\n", + "\n", + "This bloq has a gate cost of $O(\\tilde{m} \\log n)$ (see Eq 142 and paragraph below).\n", + "\n", + "#### Parameters\n", + " - `inst`: the kXOR instance $\\mathcal{I}$.\n", + " - `eps`: Precision of the prepared state (defaults to 1e-6). \n", + "\n", + "#### Registers\n", + " - `S`: a scope of $k$ variables, each in $[n]$. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Equation 134.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1dfcd5d5", + "metadata": { + "cq.autogen": "SimpleGuidingState.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import SimpleGuidingState" + ] + }, + { + "cell_type": "markdown", + "id": "41cb6141", + "metadata": { + "cq.autogen": "SimpleGuidingState.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86264fa1", + "metadata": { + "cq.autogen": "SimpleGuidingState.simple_guiding_state_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat import KXorInstance\n", + "\n", + "n, m, k = sympy.symbols(\"n m k\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "simple_guiding_state_symb = SimpleGuidingState(inst)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ec2c290", + "metadata": { + "cq.autogen": "SimpleGuidingState.simple_guiding_state" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import Constraint, KXorInstance\n", + "\n", + "inst = KXorInstance(\n", + " n=4,\n", + " k=2,\n", + " constraints=(\n", + " Constraint(S=(0, 1), b=1),\n", + " Constraint(S=(2, 3), b=-1),\n", + " Constraint(S=(1, 2), b=1),\n", + " ),\n", + ")\n", + "simple_guiding_state = SimpleGuidingState(inst)" + ] + }, + { + "cell_type": "markdown", + "id": "7142e4ee", + "metadata": { + "cq.autogen": "SimpleGuidingState.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bde3329c", + "metadata": { + "cq.autogen": "SimpleGuidingState.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([simple_guiding_state_symb, simple_guiding_state],\n", + " ['`simple_guiding_state_symb`', '`simple_guiding_state`'])" + ] + }, + { + "cell_type": "markdown", + "id": "905d7bbc", + "metadata": { + "cq.autogen": "SimpleGuidingState.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4eff0b6e", + "metadata": { + "cq.autogen": "SimpleGuidingState.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "simple_guiding_state_symb_g, simple_guiding_state_symb_sigma = simple_guiding_state_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(simple_guiding_state_symb_g)\n", + "show_counts_sigma(simple_guiding_state_symb_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "1c74c489", + "metadata": { + "cq.autogen": "GuidingState.bloq_doc.md" + }, + "source": [ + "## `GuidingState`\n", + "Prepare a guiding state for a kXOR instance with parameter $\\ell$.\n", + "\n", + "Given an kXOR instance $\\mathcal{I}$, and parameter $\\ell$ (a multiple of $k$),\n", + "we want to prepare the unit-length guiding state $|\\mathbb{\\Psi}\\rangle$ (Eq 135):\n", + "\n", + " $$\n", + " |\\mathbb{\\Psi}\\rangle\n", + " \\propto\n", + " |\\Gamma^\\ell(\\mathcal{A})\\rangle\n", + " \\propto\n", + " \\sum_{T \\in {[n] \\choose \\ell}}\n", + " \\sum_{\\{S_1, \\ldots, S_c\\} \\in \\text{Part}_k(T)}\n", + " \\left(\n", + " \\prod_{j = 1}^c B_{\\mathcal{I}}(S)\n", + " \\right)\n", + " |T\\rangle\n", + " $$\n", + "\n", + "This bloq prepares the state (Eq 136):\n", + " $$ \\beta |\\mathbb{\\Psi}\\rangle |0^{\\ell \\log \\ell + 3}\\rangle\n", + " + |\\perp\\rangle |1\\rangle\n", + " $$\n", + "where $\\beta \\ge \\Omega(1 / \\ell^{\\ell/2})$,\n", + "and $\\tilde{m}$ is the number of constraints in $\\mathcal{I}$.\n", + "\n", + "This has a gate cost of $O(\\ell \\tilde{m} \\log n)$.\n", + "\n", + "#### Parameters\n", + " - `inst`: the kXOR instance $\\mathcal{I}$.\n", + " - `ell`: the Kikuchi parameter $\\ell$.\n", + " - `amplitude_good_part`: (optional) the amplitude $\\beta$ of the guiding state $|\\Psi\\rangle$ Defaults to $\\beta = 0.99 / \\ell^{\\ell/2}$.\n", + " - `eps`: Precision of the prepared state (defaults to 1e-6). \n", + "\n", + "#### Registers\n", + " - `T`: $\\ell$ indices each in $[n]$.\n", + " - `ancilla`: (entangled) $\\ell\\log\\ell+3$ ancilla qubits used for state preparation. The all zeros state of the ancilla is the good subspace. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Section 4.4.1 \"Preparing the guiding state\", Theorem 4.15. Eq 136.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ce2e5d7", + "metadata": { + "cq.autogen": "GuidingState.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import GuidingState" + ] + }, + { + "cell_type": "markdown", + "id": "40aa8f21", + "metadata": { + "cq.autogen": "GuidingState.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00cf8dde", + "metadata": { + "cq.autogen": "GuidingState.guiding_state_symb_c" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat import KXorInstance\n", + "\n", + "n, m, c = sympy.symbols(\"n m c\", positive=True, integer=True)\n", + "k = sympy.symbols(\"k\", positive=True, integer=True, even=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "guiding_state_symb_c = GuidingState(inst, ell=c * k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10ade09c", + "metadata": { + "cq.autogen": "GuidingState.guiding_state_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat import KXorInstance\n", + "\n", + "n, m, k = sympy.symbols(\"n m k\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "c = 2\n", + "guiding_state_symb = GuidingState(inst, ell=c * inst.k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3176444f", + "metadata": { + "cq.autogen": "GuidingState.guiding_state" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import Constraint, KXorInstance\n", + "\n", + "inst = KXorInstance(\n", + " n=4,\n", + " k=2,\n", + " constraints=(\n", + " Constraint(S=(0, 1), b=1),\n", + " Constraint(S=(2, 3), b=-1),\n", + " Constraint(S=(1, 2), b=1),\n", + " ),\n", + ")\n", + "guiding_state = GuidingState(inst, ell=4)" + ] + }, + { + "cell_type": "markdown", + "id": "98c7518d", + "metadata": { + "cq.autogen": "GuidingState.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f8e52e4", + "metadata": { + "cq.autogen": "GuidingState.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([guiding_state_symb_c, guiding_state_symb, guiding_state],\n", + " ['`guiding_state_symb_c`', '`guiding_state_symb`', '`guiding_state`'])" + ] + }, + { + "cell_type": "markdown", + "id": "dc2396af", + "metadata": { + "cq.autogen": "GuidingState.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f37b9415", + "metadata": { + "cq.autogen": "GuidingState.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "guiding_state_symb_c_g, guiding_state_symb_c_sigma = guiding_state_symb_c.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(guiding_state_symb_c_g)\n", + "show_counts_sigma(guiding_state_symb_c_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/max_k_xor_sat/guiding_state.py b/qualtran/bloqs/max_k_xor_sat/guiding_state.py new file mode 100644 index 000000000..d801591ae --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guiding_state.py @@ -0,0 +1,410 @@ +# 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. +r"""Prepare the guiding state for a kXOR instance $\mathcal{I}$ with +Kikuchi parameter $\ell$. + +References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.1, Theorem 4.15. +""" +from functools import cached_property + +from attrs import evolve, field, frozen + +from qualtran import ( + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QAny, + QBit, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import XGate +from qualtran.bloqs.bookkeeping import Partition +from qualtran.bloqs.max_k_xor_sat.resource.phase_gradient import AcquirePhaseGradient +from qualtran.bloqs.mcmt import MultiControlX +from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle +from qualtran.bloqs.state_preparation.sparse_state_preparation_via_rotations import ( + SparseStatePreparationViaRotations, +) +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ceil, is_symbolic, log2, pi, SymbolicFloat, SymbolicInt +from qualtran.symbolics.simplification import extract_int + +from .arithmetic import HasDuplicates, SortInPlace +from .kxor_instance import KXorInstance +from .resource.phase_gradient import ignore_resource_alloc_free +from .shims import ProbabilisticUncompute + + +@frozen +class SimpleGuidingState(PrepareOracle): + r"""Prepare the guiding state for $\ell = k$. + + Given an kXOR instance $\mathcal{I}$, prepare the guiding state for + parameter $\ell = k$ (i.e. $c = 1$), defined in Eq 134: + $$ + |\phi\rangle + \propto + |\Gamma^k(\mathcal{A})\rangle + = + \frac{1}{\sqrt{\tilde{m}}} + \sum_{S \in {[n] \choose k}} B_\mathcal{I}(S) |S\rangle + $$ + + Here, $\tilde{m}$ is the number of constraints in the input instance $\mathcal{I}$, + and $\mathcal{A} = \sqrt{\frac{{n\choose k}}{\tilde{m}}} \mathcal{I}$. + + This bloq has a gate cost of $O(\tilde{m} \log n)$ (see Eq 142 and paragraph below). + + Args: + inst: the kXOR instance $\mathcal{I}$. + eps: Precision of the prepared state (defaults to 1e-6). + + Registers: + S: a scope of $k$ variables, each in $[n]$. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Equation 134. + """ + + inst: KXorInstance + eps: SymbolicFloat = field(default=1e-6, kw_only=True) + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(S=QAny(self.target_bitsize)) + + @property + def target_bitsize(self): + """number of bits to represent a k-subset S""" + return self.inst.k * self.inst.index_bitsize + + @property + def selection_registers(self) -> tuple[Register, ...]: + """TODO upgrade this interface""" + return (Register('S', QAny(self.target_bitsize)),) + + @property + def phasegrad_bitsize(self) -> SymbolicInt: + return ceil(log2(2 * pi(self.eps) / self.eps)) + + @property + def _state_prep_bloq(self) -> SparseStatePreparationViaRotations: + N = 2**self.target_bitsize + + if self.inst.is_symbolic(): + bloq = SparseStatePreparationViaRotations.from_n_coeffs( + N, self.inst.num_unique_constraints, phase_bitsize=self.phasegrad_bitsize + ) + else: + assert not is_symbolic(self.inst.batched_scopes) + + bloq = SparseStatePreparationViaRotations.from_coefficient_map( + N, + {self.inst.scope_as_int(S): B_I for S, B_I in self.inst.batched_scopes}, + self.phasegrad_bitsize, + ) + + bloq = evolve(bloq, target_bitsize=self.target_bitsize) + return bloq + + def build_composite_bloq(self, bb: 'BloqBuilder', S: Soquet) -> dict[str, 'SoquetT']: + phase_grad = bb.add(AcquirePhaseGradient(self.phasegrad_bitsize)) + S, phase_grad = bb.add(self._state_prep_bloq, target_state=S, phase_gradient=phase_grad) + bb.add(AcquirePhaseGradient(self.phasegrad_bitsize).adjoint(), phase_grad=phase_grad) + return {'S': S} + + +@bloq_example +def _simple_guiding_state() -> SimpleGuidingState: + from qualtran.bloqs.max_k_xor_sat import Constraint, KXorInstance + + inst = KXorInstance( + n=4, + k=2, + constraints=( + Constraint(S=(0, 1), b=1), + Constraint(S=(2, 3), b=-1), + Constraint(S=(1, 2), b=1), + ), + ) + simple_guiding_state = SimpleGuidingState(inst) + return simple_guiding_state + + +@bloq_example +def _simple_guiding_state_symb() -> SimpleGuidingState: + import sympy + + from qualtran.bloqs.max_k_xor_sat import KXorInstance + + n, m, k = sympy.symbols("n m k", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + simple_guiding_state_symb = SimpleGuidingState(inst) + return simple_guiding_state_symb + + +_SIMPLE_GUIDING_STATE_DOC = BloqDocSpec( + bloq_cls=SimpleGuidingState, examples=[_simple_guiding_state_symb, _simple_guiding_state] +) + + +@frozen +class GuidingState(PrepareOracle): + r"""Prepare a guiding state for a kXOR instance with parameter $\ell$. + + Given an kXOR instance $\mathcal{I}$, and parameter $\ell$ (a multiple of $k$), + we want to prepare the unit-length guiding state $|\mathbb{\Psi}\rangle$ (Eq 135): + + $$ + |\mathbb{\Psi}\rangle + \propto + |\Gamma^\ell(\mathcal{A})\rangle + \propto + \sum_{T \in {[n] \choose \ell}} + \sum_{\{S_1, \ldots, S_c\} \in \text{Part}_k(T)} + \left( + \prod_{j = 1}^c B_{\mathcal{I}}(S) + \right) + |T\rangle + $$ + + This bloq prepares the state (Eq 136): + $$ \beta |\mathbb{\Psi}\rangle |0^{\ell \log \ell + 3}\rangle + + |\perp\rangle |1\rangle + $$ + where $\beta \ge \Omega(1 / \ell^{\ell/2})$, + and $\tilde{m}$ is the number of constraints in $\mathcal{I}$. + + This has a gate cost of $O(\ell \tilde{m} \log n)$. + + Args: + inst: the kXOR instance $\mathcal{I}$. + ell: the Kikuchi parameter $\ell$. + amplitude_good_part: (optional) the amplitude $\beta$ of the guiding state $|\Psi\rangle$ + Defaults to $\beta = 0.99 / \ell^{\ell/2}$. + eps: Precision of the prepared state (defaults to 1e-6). + + Registers: + T: $\ell$ indices each in $[n]$. + ancilla (RIGHT): (entangled) $\ell\log\ell+3$ ancilla qubits used for state preparation. + The all zeros state of the ancilla is the good subspace. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.1 "Preparing the guiding state", Theorem 4.15. Eq 136. + """ + + inst: KXorInstance + ell: SymbolicInt + amplitude_good_part: SymbolicFloat = field(kw_only=True) + eps: SymbolicFloat = field(default=1e-6, kw_only=True) + + @amplitude_good_part.default + def _default_amplitude(self): + return self.coeff_good + + @property + def signature(self) -> 'Signature': + return Signature( + [ + Register('T', QAny(self.target_bitsize)), + Register('ancilla', QAny(self.ancilla_bitsize)), + ] + ) + + @property + def target_bitsize(self) -> SymbolicInt: + return self.inst.index_bitsize * self.ell + + @property + def ancilla_bitsize(self) -> SymbolicInt: + r"""total number of entangled ancilla. + + $\ell \log \ell$ for sorting, and 3 flag qubits. + """ + return self.sort_ancilla_bitsize + 3 + + @property + def selection_registers(self) -> tuple[Register, ...]: + """TODO upgrade this interface""" + return (Register('T', QAny(self.target_bitsize)),) + + @property + def junk_registers(self) -> tuple[Register, ...]: + """TODO upgrade this interface""" + return (Register('ancilla', QAny(self.ancilla_bitsize)),) + + @property + def sort_ancilla_bitsize(self): + r"""Number of entangled ancilla generated by the sorting algorithm. + + This is a sequence of $\ell$ numbers, each in $[\ell]$, therefore is $\ell \lceil \log \ell \rceil$. + """ + logl = ceil(log2(self.ell)) + return self.ell * logl + + @property + def c(self) -> SymbolicInt: + r"""Value of $c = \ell / k$.""" + c = self.ell // self.inst.k + c = extract_int(c) + return c + + @property + def simple_guiding_state(self) -> SimpleGuidingState: + r"""The simple guiding state $|\phi\rangle$ + + This is the simple guiding state defined in Eq. 142, + which is proportional to $|\Gamma^k\rangle$ (Eq. 134). + We will use $c$ copies of this state to prepare the required guiding state. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.1 "Preparing the guiding state", Eq. 134. + """ + return SimpleGuidingState(self.inst, eps=self.eps / self.c) + + @property + def _index_dtype(self) -> QUInt: + return QUInt(self.inst.index_bitsize) + + def build_composite_bloq( + self, bb: 'BloqBuilder', T: 'Soquet', ancilla: 'Soquet' + ) -> dict[str, 'SoquetT']: + if is_symbolic(self.c): + raise DecomposeTypeError(f"cannot decompose {self} with symbolic c=l/k={self.c}") + + partition_T_to_S = Partition( + self.target_bitsize, + (Register('S', dtype=QAny(self.simple_guiding_state.target_bitsize), shape=(self.c,)),), + ) + + partition_ancilla = Partition( + self.ancilla_bitsize, + ( + Register('ancilla', QAny(self.sort_ancilla_bitsize)), + Register('flags', QBit(), shape=(3,)), + ), + ) + + ancilla, [flag_duplicates, flag_uncompute, flag] = bb.add(partition_ancilla, x=ancilla) + + # Equation 144: |Phi> = |phi>^{\otimes c} + S = bb.add(partition_T_to_S, x=T) + for i in range(self.c): + S[i] = bb.add(self.simple_guiding_state, S=S[i]) + T = bb.add(partition_T_to_S.adjoint(), S=S) + + # sort T using `l log l` entangled clean ancilla + T, ancilla = bb.add(SortInPlace(self.ell, self._index_dtype), input=T, ancilla=ancilla) + + # mark if T has duplicates (i.e. not disjoint) (Eq 145) + T, flag_duplicates = bb.add( + HasDuplicates(self.ell, self._index_dtype), input=T, flag=flag_duplicates + ) + + # probabilistically uncompute the sorting ancilla, and mark in a flag bit + # note: flag is 0 for success (like syscall/c exit codes) + ancilla, flag_uncompute = bb.add( + ProbabilisticUncompute(self.sort_ancilla_bitsize), q=ancilla, flag=flag_uncompute + ) + + # compute the overall flag using OR, to obtain Eq 130. + [flag_duplicates, flag_uncompute], flag = bb.add( + MultiControlX(cvs=(0, 0)), controls=[flag_duplicates, flag_uncompute], target=flag + ) + flag = bb.add(XGate(), q=flag) + + # join all the ancilla into a single bag of bits + ancilla = bb.add( + partition_ancilla.adjoint(), + ancilla=ancilla, + flags=[flag_duplicates, flag_uncompute, flag], + ) + + return {'T': T, 'ancilla': ancilla} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return { + self.simple_guiding_state: self.c, + SortInPlace(self.ell, self._index_dtype): 1, + HasDuplicates(self.ell, self._index_dtype): 1, + ProbabilisticUncompute(self.sort_ancilla_bitsize): 1, + MultiControlX(cvs=(0, 0)): 1, + XGate(): 1, + } + + @cached_property + def coeff_good(self): + """lower bound on beta, the coefficient of the good state. + + Sentence below Eq. 147. + """ + return 0.99 / 2 ** (self.sort_ancilla_bitsize / 2) + + +@bloq_example(generalizer=[ignore_resource_alloc_free]) +def _guiding_state() -> GuidingState: + from qualtran.bloqs.max_k_xor_sat import Constraint, KXorInstance + + inst = KXorInstance( + n=4, + k=2, + constraints=( + Constraint(S=(0, 1), b=1), + Constraint(S=(2, 3), b=-1), + Constraint(S=(1, 2), b=1), + ), + ) + guiding_state = GuidingState(inst, ell=4) + return guiding_state + + +@bloq_example(generalizer=[ignore_resource_alloc_free]) +def _guiding_state_symb() -> GuidingState: + import sympy + + from qualtran.bloqs.max_k_xor_sat import KXorInstance + + n, m, k = sympy.symbols("n m k", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + c = 2 + guiding_state_symb = GuidingState(inst, ell=c * inst.k) + return guiding_state_symb + + +@bloq_example(generalizer=[ignore_resource_alloc_free]) +def _guiding_state_symb_c() -> GuidingState: + import sympy + + from qualtran.bloqs.max_k_xor_sat import KXorInstance + + n, m, c = sympy.symbols("n m c", positive=True, integer=True) + k = sympy.symbols("k", positive=True, integer=True, even=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + guiding_state_symb_c = GuidingState(inst, ell=c * k) + return guiding_state_symb_c + + +_GUIDING_STATE_DOC = BloqDocSpec( + bloq_cls=GuidingState, examples=[_guiding_state_symb_c, _guiding_state_symb, _guiding_state] +) diff --git a/qualtran/bloqs/max_k_xor_sat/guiding_state_test.py b/qualtran/bloqs/max_k_xor_sat/guiding_state_test.py new file mode 100644 index 000000000..2b9e3cff7 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/guiding_state_test.py @@ -0,0 +1,128 @@ +# 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 unittest.mock import ANY + +import pytest +import sympy + +from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost +from qualtran.symbolics import bit_length, ceil, log2 + +from .guiding_state import ( + _guiding_state, + _guiding_state_symb, + _guiding_state_symb_c, + _simple_guiding_state, + _simple_guiding_state_symb, +) + + +@pytest.mark.parametrize( + "bloq_ex", + [ + _simple_guiding_state, + _simple_guiding_state_symb, + _guiding_state, + _guiding_state_symb, + _guiding_state_symb_c, + ], +) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +def test_t_cost_simple(): + bloq = _simple_guiding_state() + gc = get_cost_value(bloq, QECGatesCost()) + B_GRAD = bloq.phasegrad_bitsize + + assert gc == GateCounts(and_bloq=24, toffoli=3 * (B_GRAD - 2), clifford=ANY, measurement=ANY) + + +def test_t_cost_simple_symb(): + bloq = _simple_guiding_state_symb() + gc = get_cost_value(bloq, QECGatesCost()) + B_GRAD = bloq.phasegrad_bitsize + + n, m, k = bloq.inst.n, bloq.inst.m, bloq.inst.k + klogn = k * ceil(log2(n)) + # https://github.com/quantumlib/Qualtran/issues/1341 + klogn_roundtrip = bit_length(2**klogn - 1) + + assert gc == GateCounts( + # O(k m log n) + and_bloq=4 * m + (2 * m + 1) * (klogn_roundtrip - 1) - 4, + toffoli=2 * (B_GRAD - 2), + clifford=ANY, + measurement=ANY, + ) + + +def test_t_cost(): + bloq = _guiding_state() + gc = get_cost_value(bloq, QECGatesCost()) + B_GRAD = bloq.simple_guiding_state.phasegrad_bitsize + + assert gc == GateCounts( + and_bloq=364, toffoli=6 * (B_GRAD - 2), cswap=192, clifford=ANY, measurement=ANY + ) + + +@pytest.mark.parametrize("bloq_ex", [_guiding_state_symb, _guiding_state_symb_c]) +def test_t_cost_symb_c(bloq_ex): + bloq = bloq_ex() + gc = get_cost_value(bloq, QECGatesCost()) + B_GRAD = bloq.simple_guiding_state.phasegrad_bitsize + + n, m, k = bloq.inst.n, bloq.inst.m, bloq.inst.k + l, c = bloq.ell, bloq.c + + logn = ceil(log2(n)) + logl = ceil(log2(l)) + + klogn = k * logn + # https://github.com/quantumlib/Qualtran/issues/1341 + klogn_roundtrip = bit_length(2**klogn - 1) + + assert gc == GateCounts( + and_bloq=( + 6 * l**2 * (2 * logn + 1) + + l * logl + + l + + c * (4 * m + (2 * m + 1) * (klogn_roundtrip - 1) - 4) + + (2 * l - 2) * (2 * logn + 1) + - 2 + ), + toffoli=c * (2 * (B_GRAD - 2)), + cswap=6 * l**2 * logn, + clifford=ANY, + measurement=ANY, + ) + + # verify big_O + t_cost = gc.total_t_count() + t_cost = sympy.sympify(t_cost) + t_cost = t_cost.subs(klogn_roundtrip, klogn) + t_cost = t_cost.simplify() + assert t_cost in big_O(l * m * logn + l**2 * logn + B_GRAD * c) + + +@pytest.mark.notebook +def test_notebook(): + from qualtran.testing import execute_notebook + + execute_notebook('guiding_state') diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list.py new file mode 100644 index 000000000..2032ab177 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list.py @@ -0,0 +1,346 @@ +# 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. +# +# 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 collections import Counter + +import sympy +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + QAny, + QBit, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic import AddK, Equals, Xor +from qualtran.bloqs.basic_gates import CNOT, ZeroEffect, ZeroState +from qualtran.bloqs.mcmt import And +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import SymbolicInt + +from .arithmetic import SymmetricDifference +from .kxor_instance import KXorInstance + + +@frozen +class ColumnOfKthNonZeroEntry(Bloq): + r"""Given $(S, k)$, compute the column of the $k$-th non-zero entry in row $S$. + + If the output is denoted as $f(S, k)$, then this bloq maps + $(S, k, z, b)$ to $(S, k, z \oplus f'(S, k), b \oplus (k \ge s))$. + where $s$ is the sparsity, and $f'(S, k)$ is by extending $f$ + such that for all $k \ge s$, $f'(S, k) = k$. + Using $f'$ ensures the computation is reversible. + Note: we must use the same extension $f'$ for both oracles. + + This algorithm is described by the following pseudo-code: + ``` + def forward(S, k) -> f_S_k: + nnz := 0 # counter + for j in range(\bar{m}): + T := S \Delta U_j + if |T| == l: + nnz := nnz + 1 + if nnz == k: + f_S_k ^= T + ``` + + Args: + inst: the kXOR instance $\mathcal{I}$. + ell: Kikuchi parameter $\ell$. + + Registers: + S: index register to store $S \in {[n] \choose \ell}$. + k: non-zero entry index register + T: index register to store output $T = f(S, k) \in {[n] \choose \ell}$. + """ + + inst: KXorInstance + ell: SymbolicInt + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + S=QAny(self.index_bitsize), + k=QAny(self.index_bitsize), + T=QAny(self.index_bitsize), + flag=QBit(), + ) + + @property + def index_bitsize(self) -> SymbolicInt: + return self.ell * self.inst.index_bitsize + + def adjoint(self) -> 'ColumnOfKthNonZeroEntry': + return self + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + m = self.inst.num_unique_constraints + ell, k = self.ell, self.inst.k + logn = self.inst.index_bitsize + + counts_forward = Counter[Bloq]() + + # compute symmetric differences for each constraint + counts_forward[SymmetricDifference(ell, k, ell, logn)] += m + + # counter + counts_forward[AddK(logn, 1).controlled()] += m + + # compare counter each time + counts_forward[Equals(QAny(logn))] += m + + # when counter is equal (and updated in this iteration), we can copy the result + counts_forward[And()] += m + counts_forward[CNOT()] += m # flip the final flag (flipped at most once) + + ### all counts + counts = Counter[Bloq]() + + # copy the index (controlled by the final flag) + counts[Xor(QAny(logn)).controlled()] += m + + # if nothing matched (final flag = 0), copy k and flip the flag bit + counts[Xor(QAny(logn)).controlled()] += 1 + counts[Xor(QBit())] += 1 + + for bloq, nb in counts_forward.items(): + # compute and uncompute all intermediate values. + counts[bloq] += nb + counts[bloq.adjoint()] += nb + + return counts + + +@bloq_example +def _col_kth_nz() -> ColumnOfKthNonZeroEntry: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import example_kxor_instance + + inst = example_kxor_instance() + ell = 8 + + col_kth_nz = ColumnOfKthNonZeroEntry(inst, ell) + return col_kth_nz + + +@bloq_example +def _col_kth_nz_symb() -> ColumnOfKthNonZeroEntry: + n, m, k, c, s = sympy.symbols("n m k c s", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + ell = c * k + + col_kth_nz_symb = ColumnOfKthNonZeroEntry(inst, ell) + return col_kth_nz_symb + + +@frozen +class IndexOfNonZeroColumn(Bloq): + r"""Given $(S, T)$, compute $k$ such that $T$ is the $k$-th non-zero entry in row $S$. + + If $f(S, k)$ denotes the $k$-th non-zero entry in row $S$, + then this bloq maps $(S, f'(S, k), z, b)$ to $(S, f'(S, k), z \oplus k, b \oplus )$. + where $s$ is the sparsity, and $f'(S, k)$ is by extending $f$ + such that for all $k \ge s$, $f'(S, k) = k$. + Using $f'$ ensures the computation is reversible. + Note: we must use the same extension $f'$ for both oracles. + + This algorithm is described by the following pseudo-code: + ``` + def reverse(S, f_S_k) -> k: + nnz := 0 # counter + for j in range(\bar{m}): + T := S \Delta U_j + if |T| == l: + nnz := nnz + 1 + if T == f_S_k: + k ^= nnz + ``` + + Args: + inst: the kXOR instance $\mathcal{I}$. + ell: Kikuchi parameter $\ell$. + + Registers: + S: index register to store $S \in {[n] \choose \ell}$. + k: non-zero entry index register + """ + + inst: KXorInstance + ell: SymbolicInt + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + S=QAny(self.index_bitsize), + T=QAny(self.index_bitsize), + k=QAny(self.index_bitsize), + flag=QBit(), + ) + + @property + def index_bitsize(self) -> SymbolicInt: + return self.ell * self.inst.index_bitsize + + def adjoint(self) -> 'IndexOfNonZeroColumn': + return self + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + m = self.inst.num_unique_constraints + ell, k = self.ell, self.inst.k + logn = self.inst.index_bitsize + + counts_forward = Counter[Bloq]() + + # compute symmetric differences for each constraint + counts_forward[SymmetricDifference(ell, k, ell, logn)] += m + + # counter + counts_forward[AddK(logn, 1).controlled()] += m + + # compare T to f_S_k each time + counts_forward[Equals(QAny(self.index_bitsize))] += m + + # when T is equal (and counter is updated in this iteration), we can copy the result + counts_forward[And()] += m + counts_forward[CNOT()] += m # flip the final flag (flipped at most once) + + ### all counts + counts = Counter[Bloq]() + + # copy the value of nnz (when final flag = 1) + counts[Xor(QAny(logn)).controlled()] += m + + # if nothing matched (final flag = 0), copy k and flip the flag bit + counts[Xor(QAny(logn)).controlled()] += 1 + counts[Xor(QBit())] += 1 + + for bloq, nb in counts_forward.items(): + # compute and uncompute all intermediate values. + counts[bloq] += nb + counts[bloq.adjoint()] += nb + + return counts + + +@frozen +class KikuchiNonZeroIndex(Bloq): + r"""Adjacency list oracle $O_F$ for the Kikuchi matrix. + + The oracle $O_F$ (Definition 4.5) takes in $i, k$, + and outputs $i, f(i, k)$ where $f(i, k)$ is + index of the $k$-th non-zero entry in row $i$. + + As the Kikuchi matrix is symmetric, we can use the same oracle for both rows and columns. + + The Kikuchi matrix is indexed by $S \in {[n] \choose k}$. + For a given row $S$ and column $T$, the entry $\mathcal{K}_{k}_{S, T}$ + is potentially non-zero if $S \Delta T = U_j$ for some $j$, which is + equivalent to $T = S \Delta U_j$. + Here, $U_j$ is the $j$-th unique scope in the instance $\mathcal{I}$. + + To find the $k$-th non-zero entry, we use two oracles: + 1. $(S, k) \mapsto f(S, k)$, implemented by `ColumnOfKthNonZeroEntry` + 2. $(S, f(S, k)) \mapsto k$, implemented by `IndexOfNonZeroColumn`. + + Both these above oracles are unitary: they do not have any entangled ancilla/junk registers. + + + Note on sparsity: This bloq expects the user to provide the sparsity, as it is in general + difficult to compute the precise sparsity of the Kikuchi matrix efficiently. As long as the + provided number is at least the true sparsity, the algorithm will work as expected. + In case the provides sparsity is smaller, it is equivalent to making the remaining entries zero in the final block encoding. + + Args: + inst: the kXOR instance $\mathcal{I}$. + ell: Kikuchi parameter $\ell$. + s: sparsity, i.e. max number of non-zero entries in a row/column. + + Registers: + i: integer in [2^N] + k: integer in [2^N] + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Theorem 4.17, proof para 4 (top of page 39). + """ + + inst: KXorInstance + ell: SymbolicInt + s: SymbolicInt + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(S=QAny(self.index_bitsize), k=QAny(self.index_bitsize)) + + @property + def index_bitsize(self) -> SymbolicInt: + return self.ell * self.inst.index_bitsize + + def build_composite_bloq( + self, bb: 'BloqBuilder', S: 'Soquet', k: 'Soquet' + ) -> dict[str, 'SoquetT']: + T = bb.allocate(self.index_bitsize) + flag = bb.add(ZeroState()) + S, k, T, flag = bb.add( + ColumnOfKthNonZeroEntry(self.inst, self.ell), S=S, k=k, T=T, flag=flag + ) + S, T, k, flag = bb.add(IndexOfNonZeroColumn(self.inst, self.ell), S=S, T=T, k=k, flag=flag) + bb.free(k) + bb.add(ZeroEffect(), q=flag) + return dict(S=S, k=T) + + +@bloq_example +def _kikuchi_nonzero_index() -> KikuchiNonZeroIndex: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import example_kxor_instance + + inst = example_kxor_instance() + ell = 8 + s = inst.brute_force_sparsity(ell) + + kikuchi_nonzero_index = KikuchiNonZeroIndex(inst, ell, s=s) + return kikuchi_nonzero_index + + +@bloq_example +def _kikuchi_nonzero_index_symb() -> KikuchiNonZeroIndex: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance + + n, m, k, c, s = sympy.symbols("n m k c s", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + ell = c * k + + kikuchi_nonzero_index_symb = KikuchiNonZeroIndex(inst, ell, s=s) + return kikuchi_nonzero_index_symb + + +_KIKUCHI_NONZERO_INDEX_DOC = BloqDocSpec( + bloq_cls=KikuchiNonZeroIndex, examples=[_kikuchi_nonzero_index_symb, _kikuchi_nonzero_index] +) diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list_test.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list_test.py new file mode 100644 index 000000000..d9523c747 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_list_test.py @@ -0,0 +1,72 @@ +# 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 unittest.mock import ANY + +import pytest +import sympy + +from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost +from qualtran.symbolics import ceil, log2 + +from .kikuchi_adjacency_list import ( + _col_kth_nz, + _col_kth_nz_symb, + _kikuchi_nonzero_index, + _kikuchi_nonzero_index_symb, +) + + +@pytest.mark.parametrize( + "bloq_ex", + [_col_kth_nz, _col_kth_nz_symb, _kikuchi_nonzero_index, _kikuchi_nonzero_index_symb], + ids=lambda bloq_ex: bloq_ex.name, +) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +def test_cost_col_kth_nz(): + n, m, k, c, s = sympy.symbols("n m k c s", positive=True, integer=True) + l = c * k + logn = ceil(log2(n)) + logl = ceil(log2(l)) + + bloq = _col_kth_nz_symb() + cost = get_cost_value(bloq, QECGatesCost()) + assert cost == GateCounts( + toffoli=(m + 1) * logn, + cswap=4 * l * m * (logl + 1) * logn, + and_bloq=( + 4 * m * (logn - 1) + + ( + 2 + * m + * ( + 2 * l * ((2 * logn + 1) * (logl + 1)) + + l + + k + + 2 * ((logn - 1) * (l + k - 1)) + + 2 * ceil(log2(l + k)) + - 4 + ) + ) + + m + ), + clifford=ANY, + measurement=ANY, + ) + assert big_O(cost.total_t_count()) == big_O(l * m * logn * logl) diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix.py new file mode 100644 index 000000000..c9e99b57b --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix.py @@ -0,0 +1,190 @@ +# 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. +# +# 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 collections import Counter +from functools import cached_property +from typing import Optional + +import attrs +import sympy +from attrs import frozen + +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqDocSpec, + CtrlSpec, + QAny, + QBit, + QFxp, + Signature, +) +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import SymbolicInt + +from .arithmetic import SymmetricDifference +from .kxor_instance import KXorInstance +from .load_kxor_instance import LoadUniqueScopeIndex, PRGAUniqueConstraintRHS + + +@frozen +class KikuchiMatrixEntry(Bloq): + r"""Adjacency matrix oracle for the Kikuchi matrix. + + Given a kXOR instance $\mathcal{I}$ with $n$ variables, $m$ constraints, + the Kikuchi matrix with parameter $\ell$ is indexed by ${[n] \choose l}$. + For $S, T \in {[n] \choose l}$, the entry is given by + $H_{S, T} = B_{\mathcal{I}}(S \Delta T)/M$, where $M$ is the max entry. + + This bloq implements the transform: + $$ + |0 \rangle |S\rangle |T\rangle + \mapsto + (\sqrt{H_{S, T}}|0\rangle + \sqrt{1 - |H_{S, T}|}|1\rangle)|S\rangle |T\rangle + $$ + + This is equivalent to $O_H$ (Def. 4.3) from the paper, but is optimized to classically + compute the `arccos` of the entries, and directly apply the rotation, + instead of computing them using a quantum circuit. + + This bloq performs the following steps + 1. Compute the symmetric difference $D = S \Delta T$. + 2. Compute the index $j$ s.t. $U_j = D$ (where $U_j$ are a list of unique scopes) + 4. Apply a controlled Y-rotation with angle for the $j$-th entry. + 5. Uncompute steps 3, 2, 1. + + Args: + inst: k-XOR instance + ell: the Kikuchi parameter $\ell$, must be a multiple of $k$. + entry_bitsize: number of bits to approximate each rotation angle to. + cv: single bit control value (0 or 1), or None for uncontrolled (default). + + Registers: + S: row index + T: column index + q: the qubit to rotate by $Ry(2 \arccos(\sqrt{H_{S,T} / M}))$ as defined above. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Definition 4.3. Theorem 4.17 para 3. + """ + + inst: KXorInstance + ell: SymbolicInt + entry_bitsize: SymbolicInt + cv: Optional[int] = None + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes( + ctrl=QAny(1 if self.cv is not None else 0), + S=QAny(self.index_bitsize), + T=QAny(self.index_bitsize), + q=QBit(), + ) + + @cached_property + def index_bitsize(self) -> SymbolicInt: + """total number of bits to store `l` indices in `[n]`.""" + return self.ell * self.inst.index_bitsize + + @cached_property + def rotation_angle_dtype(self): + return QFxp(self.entry_bitsize, self.entry_bitsize) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + counts = Counter[Bloq]() + + # S \Delta T + symm_diff = SymmetricDifference(self.ell, self.ell, self.inst.k, self.inst.index_bitsize) + counts[symm_diff] += 1 + counts[symm_diff.adjoint()] += 1 + + # Map S to j, such that U_j = S + load_idx = LoadUniqueScopeIndex(self.inst) + counts[load_idx] += 1 + counts[load_idx.adjoint()] += 1 + + # apply the rotation + rotation: Bloq = PRGAUniqueConstraintRHS(self.inst, self.entry_bitsize) + if self.cv is not None: + rotation = rotation.controlled(CtrlSpec(cvs=self.cv)) + counts[rotation] += 1 + + return counts + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.bloq_with_specialized_single_qubit_control import ( + get_ctrl_system_for_bloq_with_specialized_single_qubit_control, + ) + + return get_ctrl_system_for_bloq_with_specialized_single_qubit_control(self, ctrl_spec) + + def with_cv(self, *, cv: Optional[int]) -> 'Bloq': + return attrs.evolve(self, cv=cv) + + @property + def ctrl_reg_name(self) -> str: + return 'ctrl' + + +@bloq_example +def _kikuchi_matrix_entry() -> KikuchiMatrixEntry: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance + + n, k = 10, 4 + cs = ( + Constraint((0, 1, 2, 3), -1), + Constraint((0, 2, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((1, 2, 3, 4), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((2, 3, 4, 5), 1), + ) + inst = KXorInstance(n, k, cs) + ell = 8 + + kikuchi_matrix_entry = KikuchiMatrixEntry(inst, ell, entry_bitsize=3) + return kikuchi_matrix_entry + + +@bloq_example +def _kikuchi_matrix_entry_symb() -> KikuchiMatrixEntry: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance + + n, m, k, c = sympy.symbols("n m k c", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + ell = c * k + + kikuchi_matrix_entry_symb = KikuchiMatrixEntry(inst, ell, entry_bitsize=3) + return kikuchi_matrix_entry_symb + + +_KIKUCHI_MATRIX_ENTRY_DOC = BloqDocSpec( + bloq_cls=KikuchiMatrixEntry, examples=[_kikuchi_matrix_entry_symb, _kikuchi_matrix_entry] +) diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix_test.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix_test.py new file mode 100644 index 000000000..e3f240389 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_adjacency_matrix_test.py @@ -0,0 +1,87 @@ +# 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 unittest.mock import ANY + +import pytest +import sympy +from attrs import evolve + +from qualtran.resource_counting import big_O, GateCounts, get_cost_value, QECGatesCost +from qualtran.symbolics import ceil, log2 + +from .kikuchi_adjacency_matrix import _kikuchi_matrix_entry, _kikuchi_matrix_entry_symb + + +@pytest.mark.parametrize("bloq_ex", [_kikuchi_matrix_entry, _kikuchi_matrix_entry_symb]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +def test_controlled_cost(): + bloq = _kikuchi_matrix_entry() + _, sigma = bloq.call_graph(max_depth=2) + _, ctrl_sigma = bloq.controlled().call_graph(max_depth=2) + + # should only differ in QROM call for loading absolute amplitudes + a_minus_b = set(sigma.items()) - set(ctrl_sigma.items()) + b_minus_a = set(ctrl_sigma.items()) - set(sigma.items()) + assert len(a_minus_b) == 1 + assert len(b_minus_a) == 1 + + ((qrom, na),) = a_minus_b + ((ctrl_qrom, nb),) = b_minus_a + assert na == nb + assert evolve(qrom, num_controls=1) == ctrl_qrom # type: ignore + + +def test_cost(): + bloq = _kikuchi_matrix_entry() + + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts( + cswap=512, and_bloq=1301, clifford=12518, measurement=1301, rotation=ANY + ) + + +def test_cost_symb(): + bloq = _kikuchi_matrix_entry_symb() + n, m, k, c = sympy.symbols("n m k c", positive=True, integer=True) + + l = c * k + logl = ceil(log2(l)) + logn = ceil(log2(n)) + logm = ceil(log2(m)) + + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts( + cswap=4 * l * (logl + 1) * logn, + and_bloq=( + 4 * l * ((2 * logn + 1) * (logl + 1)) + + 4 * l + + 2 * m * (k * logn - 1) + + 2 * m + + 4 * ((2 * l - 1) * (logn - 1)) + + logm + + 4 * ceil(log2(2 * l)) + - 10 + ), + rotation=ANY, + clifford=ANY, + measurement=ANY, + ) + + assert big_O(gc.total_t_count()) == big_O(l * logn * logl + k * m * logn) diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.ipynb b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.ipynb new file mode 100644 index 000000000..6f46e04a0 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.ipynb @@ -0,0 +1,1243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1b8feca5", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Noisy kXOR: Block-encoding the Kikuchi Matrix\n", + "\n", + "Section 4.4.2 Simulating the Kikuchi Hamiltonian\n", + "\n", + "This module contains oracles to implement the block-encoding of the Kikuchi\n", + "Hamiltonian corresponding to an input k-XOR-SAT instance.\n", + "\n", + "References:\n", + " [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1)\n", + " Section 4.4.2 for algorithm. Section 2.4 for definitions and notation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cf95753", + "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": "f77a3d87", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.bloq_doc.md" + }, + "source": [ + "## `KikuchiMatrixEntry`\n", + "Adjacency matrix oracle for the Kikuchi matrix.\n", + "\n", + "Given a kXOR instance $\\mathcal{I}$ with $n$ variables, $m$ constraints,\n", + "the Kikuchi matrix with parameter $\\ell$ is indexed by ${[n] \\choose l}$.\n", + "For $S, T \\in {[n] \\choose l}$, the entry is given by\n", + "$H_{S, T} = B_{\\mathcal{I}}(S \\Delta T)/M$, where $M$ is the max entry.\n", + "\n", + "This bloq implements the transform:\n", + " $$\n", + " |0 \\rangle |S\\rangle |T\\rangle\n", + " \\mapsto\n", + " (\\sqrt{H_{S, T}}|0\\rangle + \\sqrt{1 - |H_{S, T}|}|1\\rangle)|S\\rangle |T\\rangle\n", + " $$\n", + "\n", + "This is equivalent to $O_H$ (Def. 4.3) from the paper, but is optimized to classically\n", + "compute the `arccos` of the entries, and directly apply the rotation,\n", + "instead of computing them using a quantum circuit.\n", + "\n", + "This bloq performs the following steps\n", + "1. Compute the symmetric difference $D = S \\Delta T$.\n", + "2. Compute the index $j$ s.t. $U_j = D$ (where $U_j$ are a list of unique scopes)\n", + "4. Apply a controlled Y-rotation with angle for the $j$-th entry.\n", + "5. Uncompute steps 3, 2, 1.\n", + "\n", + "#### Parameters\n", + " - `inst`: k-XOR instance\n", + " - `ell`: the Kikuchi parameter $\\ell$, must be a multiple of $k$.\n", + " - `entry_bitsize`: number of bits to approximate each rotation angle to.\n", + " - `cv`: single bit control value (0 or 1), or None for uncontrolled (default). \n", + "\n", + "#### Registers\n", + " - `S`: row index\n", + " - `T`: column index\n", + " - `q`: the qubit to rotate by $Ry(2 \\arccos(\\sqrt{H_{S,T} / M}))$ as defined above. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Definition 4.3. Theorem 4.17 para 3.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a6e1a31", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KikuchiMatrixEntry" + ] + }, + { + "cell_type": "markdown", + "id": "6b89e54d", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.bloq_doc.md" + }, + "source": [ + "## `KikuchiNonZeroIndex`\n", + "Adjacency list oracle $O_F$ for the Kikuchi matrix.\n", + "\n", + "The oracle $O_F$ (Definition 4.5) takes in $i, k$,\n", + "and outputs $i, f(i, k)$ where $f(i, k)$ is\n", + "index of the $k$-th non-zero entry in row $i$.\n", + "\n", + "As the Kikuchi matrix is symmetric, we can use the same oracle for both rows and columns.\n", + "\n", + "The Kikuchi matrix is indexed by $S \\in {[n] \\choose k}$.\n", + "For a given row $S$ and column $T$, the entry $\\mathcal{K}_{k}_{S, T}$\n", + "is potentially non-zero if $S \\Delta T = U_j$ for some $j$, which is\n", + "equivalent to $T = S \\Delta U_j$.\n", + "Here, $U_j$ is the $j$-th unique scope in the instance $\\mathcal{I}$.\n", + "\n", + "To find the $k$-th non-zero entry, we use two oracles:\n", + "1. $(S, k) \\mapsto f(S, k)$, implemented by `ColumnOfKthNonZeroEntry`\n", + "2. $(S, f(S, k)) \\mapsto k$, implemented by `IndexOfNonZeroColumn`.\n", + "\n", + "Both these above oracles are unitary: they do not have any entangled ancilla/junk registers.\n", + "\n", + "\n", + "Note on sparsity: This bloq expects the user to provide the sparsity, as it is in general\n", + "difficult to compute the precise sparsity of the Kikuchi matrix efficiently. As long as the\n", + "provided number is at least the true sparsity, the algorithm will work as expected.\n", + "In case the provides sparsity is smaller, it is equivalent to making the remaining entries zero in the final block encoding.\n", + "\n", + "#### Parameters\n", + " - `inst`: the kXOR instance $\\mathcal{I}$.\n", + " - `ell`: Kikuchi parameter $\\ell$.\n", + " - `s`: sparsity, i.e. max number of non-zero entries in a row/column. \n", + "\n", + "#### Registers\n", + " - `i`: integer in [2^N]\n", + " - `k`: integer in [2^N] \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Theorem 4.17, proof para 4 (top of page 39).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef63db06", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KikuchiNonZeroIndex" + ] + }, + { + "cell_type": "markdown", + "id": "1cb30021", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.bloq_doc.md" + }, + "source": [ + "## `KikuchiHamiltonian`\n", + "Block encoding of the Kikuchi matrix $\\mathcal{K}_\\ell$.\n", + "\n", + "This is implemented by a sparse matrix block encoding using the adjacency matrix\n", + "and adjacency list oracles.\n", + "\n", + "This assumes a default sparsity of $\\bar{m}$, which is the number of unique\n", + "scopes in the instance $\\mathcal{I}$.\n", + "If a better bound on sparsity is known, it can be passed in by the user.\n", + "\n", + "#### Parameters\n", + " - `inst`: kXOR instance $\\mathcal{I}$.\n", + " - `ell`: Kikuchi parameter $\\ell$.\n", + " - `entry_bitsize`: Number of bits $b$ to approximate the matrix entries (angles) to.\n", + " - `s`: sparsity of the Kikuchi matrix, defaults to $\\bar{m}$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba1fa5a1", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KikuchiHamiltonian" + ] + }, + { + "cell_type": "markdown", + "id": "3c9a2aea", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94da5bc4", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.kikuchi_matrix_symb" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance\n", + "\n", + "n, m, k, c = sympy.symbols(\"n m k c\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "ell = c * k\n", + "\n", + "kikuchi_matrix_symb = KikuchiHamiltonian(inst, ell, entry_bitsize=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6655ce72", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.kikuchi_matrix" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance\n", + "\n", + "n, k = 10, 4\n", + "cs = (\n", + " Constraint((0, 1, 2, 3), -1),\n", + " Constraint((0, 2, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((1, 2, 3, 4), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((2, 3, 4, 5), 1),\n", + ")\n", + "inst = KXorInstance(n, k, cs)\n", + "ell = 8\n", + "\n", + "kikuchi_matrix = KikuchiHamiltonian(inst, ell, entry_bitsize=10)" + ] + }, + { + "cell_type": "markdown", + "id": "852691fa", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17d9dfa4", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([kikuchi_matrix_symb, kikuchi_matrix],\n", + " ['`kikuchi_matrix_symb`', '`kikuchi_matrix`'])" + ] + }, + { + "cell_type": "markdown", + "id": "f3c5ab8a", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f641df92", + "metadata": { + "cq.autogen": "KikuchiHamiltonian.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "kikuchi_matrix_symb_g, kikuchi_matrix_symb_sigma = kikuchi_matrix_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(kikuchi_matrix_symb_g)\n", + "show_counts_sigma(kikuchi_matrix_symb_sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "54aa64e0-364d-425c-8ec4-f4ce988150cc", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "B[H]\n", + "inst=KXorInst ..., ell=8, entry_bitsize=10, s=6\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "B[SparseMatrixHermitian]\n", + "col_oracle=BlackBox ..., entry_oracle=BlackBox ..., eps=0, cv=None\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "PrepareUniformSuperposition\n", + "n=6, cvs=()\n", + "\n", + "\n", + "\n", + "b1->b2\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "BlackBoxKikuchiRowColumnOracle†\n", + "subbloq=BlackBox ...\n", + "\n", + "\n", + "\n", + "b1->b3\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "BlackBoxKikuchiRowColumnOracle\n", + "O_F=KikuchiN ...\n", + "\n", + "\n", + "\n", + "b1->b4\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "Swap\n", + "bitsize=1\n", + "\n", + "\n", + "\n", + "b1->b5\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "PrepareUniformSuperposition†\n", + "subbloq=AutoPart ...\n", + "\n", + "\n", + "\n", + "b1->b6\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "Swap\n", + "bitsize=24\n", + "\n", + "\n", + "\n", + "b1->b7\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "BlackBoxKikuchiEntryOracle\n", + "O_H=KikuchiM ...\n", + "\n", + "\n", + "\n", + "b1->b8\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b9\n", + "\n", + "BlackBoxKikuchiEntryOracle†\n", + "subbloq=BlackBox ...\n", + "\n", + "\n", + "\n", + "b1->b9\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b10\n", + "\n", + "LessThanConstant\n", + "bitsize=2, less_than_val=3\n", + "\n", + "\n", + "\n", + "b2->b10\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b11\n", + "\n", + "Rz\n", + "angle=1.230959 ..., eps=1e-11\n", + "\n", + "\n", + "\n", + "b2->b11\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b20\n", + "\n", + "Hadamard\n", + "\n", + "\n", + "\n", + "b2->b20\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b23\n", + "\n", + "And((0, 0))\n", + "cv1=0, cv2=0, uncompute=False\n", + "\n", + "\n", + "\n", + "b2->b23\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24\n", + "\n", + "And((0, 0))†\n", + "cv1=0, cv2=0, uncompute=True\n", + "\n", + "\n", + "\n", + "b2->b24\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b12\n", + "\n", + "KikuchiNonZeroIndex†\n", + "subbloq=KikuchiN ...\n", + "\n", + "\n", + "\n", + "b3->b12\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13\n", + "\n", + "KikuchiNonZeroIndex\n", + "inst=KXorInst ..., ell=8, s=6\n", + "\n", + "\n", + "\n", + "b4->b13\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b15\n", + "\n", + "TwoBitSwap\n", + "\n", + "\n", + "\n", + "b5->b15\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b14\n", + "\n", + "PrepareUniformSuperposition†\n", + "subbloq=PrepareU ...\n", + "\n", + "\n", + "\n", + "b6->b14\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7->b15\n", + "\n", + "\n", + "24\n", + "\n", + "\n", + "\n", + "b16\n", + "\n", + "RzViaPhaseGradient\n", + "angle_dtype=QFxp(bit ..., phasegrad_dtype=QFxp(bit ...\n", + "\n", + "\n", + "\n", + "b8->b16\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b17\n", + "\n", + "KikuchiMatrixEntry†\n", + "subbloq=KikuchiM ...\n", + "\n", + "\n", + "\n", + "b8->b17\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b19\n", + "\n", + "KikuchiMatrixEntry\n", + "inst=KXorInst ..., ell=8, entry_bitsize=10\n", + "\n", + "\n", + "\n", + "b8->b19\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b8->b20\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b9->b17\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b18\n", + "\n", + "RzViaPhaseGradient†\n", + "subbloq=RzViaPha ...\n", + "\n", + "\n", + "\n", + "b9->b18\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b9->b19\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b9->b20\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b31\n", + "\n", + "XGate\n", + "\n", + "\n", + "\n", + "b10->b31\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b33\n", + "\n", + "And((1, 1))†\n", + "cv1=1, cv2=1, uncompute=True\n", + "\n", + "\n", + "\n", + "b10->b33\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b34\n", + "\n", + "And((1, 1))\n", + "cv1=1, cv2=1, uncompute=False\n", + "\n", + "\n", + "\n", + "b10->b34\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b35\n", + "\n", + "CNOT\n", + "\n", + "\n", + "\n", + "b10->b35\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b27\n", + "\n", + "ArbitraryGate\n", + "n_ctrls=0\n", + "\n", + "\n", + "\n", + "b12->b27\n", + "\n", + "\n", + "tilde{O}(165.437600046154)\n", + "\n", + "\n", + "\n", + "b13->b27\n", + "\n", + "\n", + "tilde{O}(165.437600046154)\n", + "\n", + "\n", + "\n", + "b14->b20\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b21\n", + "\n", + "Rz\n", + "angle=-1.23095 ..., eps=1e-11\n", + "\n", + "\n", + "\n", + "b14->b21\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b22\n", + "\n", + "LessThanConstant†\n", + "subbloq=LessThan ...\n", + "\n", + "\n", + "\n", + "b14->b22\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b14->b23\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b14->b24\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b25\n", + "\n", + "ControlledAddOrSubtract\n", + "a_dtype=QUInt(bi ..., b_dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b16->b25\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b17->b27\n", + "\n", + "\n", + "tilde{O}(24)\n", + "\n", + "\n", + "\n", + "b26\n", + "\n", + "ControlledAddOrSubtract†\n", + "subbloq=Controll ...\n", + "\n", + "\n", + "\n", + "b18->b26\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b19->b27\n", + "\n", + "\n", + "tilde{O}(24)\n", + "\n", + "\n", + "\n", + "b22->b31\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b22->b33\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b22->b34\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b22->b35\n", + "\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "b28\n", + "\n", + "Add\n", + "a_dtype=QUInt(bi ..., b_dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b25->b28\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b29\n", + "\n", + "C[BitwiseNot]\n", + "subbloq=BitwiseN ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b25->b29\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b25->b31\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b26->b29\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b30\n", + "\n", + "Add†\n", + "subbloq=Add(a_dt ...\n", + "\n", + "\n", + "\n", + "b26->b30\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b26->b31\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b28->b33\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b28->b34\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b28->b35\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "\n", + "b32\n", + "\n", + "C[OnEach]\n", + "subbloq=OnEach(n ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b29->b32\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b30->b33\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b30->b34\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b30->b35\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "\n", + "b32->b35\n", + "\n", + "\n", + "10\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "#### Counts totals:\n", + " - `And((0, 0))`: 2\n", + " - `And((0, 0))†`: 2\n", + " - `And((1, 1))`: 26\n", + " - `And((1, 1))†`: 26\n", + " - `ArbitraryGate`: $\\displaystyle 4 \\tilde{O}{\\left(24 \\right)} + 2 \\tilde{O}{\\left(165.437600046154 \\right)}$\n", + " - `CNOT`: 166\n", + " - `H`: 18\n", + " - `Rz(-0.3918265520306073π)`: 2\n", + " - `Rz(0.3918265520306073π)`: 2\n", + " - `TwoBitSwap`: 25\n", + " - `XGate`: 28" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_alloc_free\n", + "g, sigma = kikuchi_matrix.call_graph(generalizer=[ignore_split_join, ignore_alloc_free])\n", + "show_call_graph(g)\n", + "show_counts_sigma(sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "0e08ec50", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71928579", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.kikuchi_matrix_entry_symb" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance\n", + "\n", + "n, m, k, c = sympy.symbols(\"n m k c\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "ell = c * k\n", + "\n", + "kikuchi_matrix_entry_symb = KikuchiMatrixEntry(inst, ell, entry_bitsize=3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b34e5549", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.kikuchi_matrix_entry" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance\n", + "\n", + "n, k = 10, 4\n", + "cs = (\n", + " Constraint((0, 1, 2, 3), -1),\n", + " Constraint((0, 2, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((1, 2, 3, 4), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((2, 3, 4, 5), 1),\n", + ")\n", + "inst = KXorInstance(n, k, cs)\n", + "ell = 8\n", + "\n", + "kikuchi_matrix_entry = KikuchiMatrixEntry(inst, ell, entry_bitsize=3)" + ] + }, + { + "cell_type": "markdown", + "id": "1cc785d1", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "701a1d9f", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([kikuchi_matrix_entry_symb, kikuchi_matrix_entry],\n", + " ['`kikuchi_matrix_entry_symb`', '`kikuchi_matrix_entry`'])" + ] + }, + { + "cell_type": "markdown", + "id": "94309337", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a78b1259", + "metadata": { + "cq.autogen": "KikuchiMatrixEntry.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "kikuchi_matrix_entry_symb_g, kikuchi_matrix_entry_symb_sigma = kikuchi_matrix_entry_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(kikuchi_matrix_entry_symb_g)\n", + "show_counts_sigma(kikuchi_matrix_entry_symb_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "6ad50b81", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45907568", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.kikuchi_nonzero_index_symb" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance\n", + "\n", + "n, m, k, c, s = sympy.symbols(\"n m k c s\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "ell = c * k\n", + "\n", + "kikuchi_nonzero_index_symb = KikuchiNonZeroIndex(inst, ell, s=s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71fb9096", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.kikuchi_nonzero_index" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import example_kxor_instance\n", + "\n", + "inst = example_kxor_instance()\n", + "ell = 8\n", + "s = inst.brute_force_sparsity(ell)\n", + "\n", + "kikuchi_nonzero_index = KikuchiNonZeroIndex(inst, ell, s=s)" + ] + }, + { + "cell_type": "markdown", + "id": "70ae2a8a", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4178dff", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([kikuchi_nonzero_index_symb, kikuchi_nonzero_index],\n", + " ['`kikuchi_nonzero_index_symb`', '`kikuchi_nonzero_index`'])" + ] + }, + { + "cell_type": "markdown", + "id": "206645a5", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "212a5f48", + "metadata": { + "cq.autogen": "KikuchiNonZeroIndex.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "kikuchi_nonzero_index_symb_g, kikuchi_nonzero_index_symb_sigma = kikuchi_nonzero_index_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(kikuchi_nonzero_index_symb_g)\n", + "show_counts_sigma(kikuchi_nonzero_index_symb_sigma)" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.py new file mode 100644 index 000000000..48e465436 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding.py @@ -0,0 +1,251 @@ +# 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. +"""Section 4.4.2 Simulating the Kikuchi Hamiltonian + +This module contains oracles to implement the block-encoding of the Kikuchi +Hamiltonian corresponding to an input k-XOR-SAT instance. + +References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.2 for algorithm. Section 2.4 for definitions and notation. +""" +from functools import cached_property + +import sympy +from attrs import field, frozen + +from qualtran import ( + bloq_example, + BloqBuilder, + BloqDocSpec, + BQUInt, + QAny, + QBit, + QUInt, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.block_encoding import BlockEncoding +from qualtran.bloqs.block_encoding.sparse_matrix import RowColumnOracle +from qualtran.bloqs.block_encoding.sparse_matrix_hermitian import ( + SparseMatrixHermitian, + SqrtEntryOracle, +) +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.symbolics import is_symbolic, SymbolicFloat, SymbolicInt + +from .kikuchi_adjacency_list import KikuchiNonZeroIndex +from .kikuchi_adjacency_matrix import KikuchiMatrixEntry +from .kxor_instance import KXorInstance + + +@frozen +class BlackBoxKikuchiEntryOracle(SqrtEntryOracle): + r"""Wrapper around the adjacency matrix oracle $O_H$ of the Kikuchi graph.""" + + O_H: KikuchiMatrixEntry + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + q=QBit(), i=QAny(self.system_bitsize), j=QAny(self.system_bitsize) + ) + + @property + def system_bitsize(self) -> SymbolicInt: + return self.O_H.index_bitsize + + @property + def epsilon(self) -> SymbolicFloat: + """precision due to fixed-point approximation of entries. + + In the good case, whp (i.e. 1 - o(1)), the entries are in [-2, 2], + whose corresponding angles can be represented exactly with 3 bits. + I.e. `arccos(sqrt(x / 2)) / pi` for `x in [-2, 2]` are `2, 1.5, 1, 0.5, 0`. + """ + return 0 + + @property + def _phasegrad_bitsize(self) -> SymbolicInt: + return self.O_H.entry_bitsize + + def build_composite_bloq( + self, bb: 'BloqBuilder', q: 'Soquet', i: 'Soquet', j: 'Soquet' + ) -> dict[str, 'SoquetT']: + i, j, q = bb.add(self.O_H, S=i, T=j, q=q) + return dict(q=q, i=i, j=j) + + +@frozen +class BlackBoxKikuchiRowColumnOracle(RowColumnOracle): + r"""Wrapper around the adjacency list oracle $O_F$ of the Kikuchi graph.""" + + O_F: KikuchiNonZeroIndex + + @cached_property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + l=BQUInt(self.system_bitsize, self.num_nonzero), i=QUInt(self.system_bitsize) + ) + + @property + def system_bitsize(self) -> SymbolicInt: + return self.O_F.index_bitsize + + @property + def num_nonzero(self) -> SymbolicInt: + return self.O_F.s + + def build_composite_bloq( + self, bb: 'BloqBuilder', l: 'Soquet', i: 'Soquet' + ) -> dict[str, 'SoquetT']: + i, l = bb.add(self.O_F, S=i, k=l) + return dict(l=l, i=i) + + +@frozen +class KikuchiHamiltonian(BlockEncoding): + r"""Block encoding of the Kikuchi matrix $\mathcal{K}_\ell$. + + This is implemented by a sparse matrix block encoding using the adjacency matrix + and adjacency list oracles. + + This assumes a default sparsity of $\bar{m}$, which is the number of unique + scopes in the instance $\mathcal{I}$. + If a better bound on sparsity is known, it can be passed in by the user. + + Args: + inst: kXOR instance $\mathcal{I}$. + ell: Kikuchi parameter $\ell$. + entry_bitsize: Number of bits $b$ to approximate the matrix entries (angles) to. + s: sparsity of the Kikuchi matrix, defaults to $\bar{m}$. + """ + + inst: KXorInstance + ell: SymbolicInt + entry_bitsize: SymbolicInt = field() + s: SymbolicInt = field() + + @s.default + def _default_sparsity(self) -> SymbolicInt: + return self.inst.num_unique_constraints + + @entry_bitsize.default + def _default_entry_bitsize(self): + if is_symbolic(self.inst.max_rhs) or self.inst.max_rhs == 2: + # one T gate suffices! + return 3 + raise ValueError("Entries outside range [-2, 2], please specify an explicit entry_bitsize.") + + @cached_property + def signature(self) -> 'Signature': + return Signature.build( + system=self.system_bitsize, ancilla=self.ancilla_bitsize, resource=self.resource_bitsize + ) + + @cached_property + def _sparse_matrix_encoding(self) -> SparseMatrixHermitian: + return SparseMatrixHermitian( + col_oracle=self._blackbox_O_F, entry_oracle=self._blackbox_O_H, eps=0 + ) + + @cached_property + def oracle_O_H(self) -> KikuchiMatrixEntry: + r"""Maps $|i, j\rangle |0\rangle$ to $|i, j\rangle (\sqrt{A_{ij}} |0\rangle + \sqrt{1 - |A_{ij}|} |1\rangle)""" + return KikuchiMatrixEntry(inst=self.inst, ell=self.ell, entry_bitsize=self.entry_bitsize) + + @cached_property + def _blackbox_O_H(self) -> SqrtEntryOracle: + """wrapped to match the interface""" + return BlackBoxKikuchiEntryOracle(self.oracle_O_H) + + @cached_property + def oracle_O_F(self) -> KikuchiNonZeroIndex: + r"""Maps `i, k` to `i, f(i, k)` where `f(i, k)` is the column of the `k`-th nonzero entry in row `i`.""" + return KikuchiNonZeroIndex(inst=self.inst, ell=self.ell, s=self.s) + + @cached_property + def _blackbox_O_F(self) -> RowColumnOracle: + """wrapped to match the interface""" + return BlackBoxKikuchiRowColumnOracle(self.oracle_O_F) + + @property + def alpha(self) -> SymbolicFloat: + return self._sparse_matrix_encoding.alpha + + @property + def system_bitsize(self) -> SymbolicInt: + return self._sparse_matrix_encoding.system_bitsize + + @property + def ancilla_bitsize(self) -> SymbolicInt: + return self._sparse_matrix_encoding.ancilla_bitsize + + @property + def resource_bitsize(self) -> SymbolicInt: + return self._sparse_matrix_encoding.resource_bitsize + + @property + def epsilon(self) -> SymbolicFloat: + return self._blackbox_O_H.epsilon + + @property + def signal_state(self) -> BlackBoxPrepare: + return self._sparse_matrix_encoding.signal_state + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + return bb.add_d(self._sparse_matrix_encoding, **soqs) + + def __str__(self): + return 'B[K_l]' + + +@bloq_example +def _kikuchi_matrix() -> KikuchiHamiltonian: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance + + n, k = 10, 4 + cs = ( + Constraint((0, 1, 2, 3), -1), + Constraint((0, 2, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((1, 2, 3, 4), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((2, 3, 4, 5), 1), + ) + inst = KXorInstance(n, k, cs) + ell = 8 + + kikuchi_matrix = KikuchiHamiltonian(inst, ell, entry_bitsize=10) + return kikuchi_matrix + + +@bloq_example +def _kikuchi_matrix_symb() -> KikuchiHamiltonian: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance + + n, m, k, c = sympy.symbols("n m k c", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + ell = c * k + + kikuchi_matrix_symb = KikuchiHamiltonian(inst, ell, entry_bitsize=10) + return kikuchi_matrix_symb + + +_KIKUCHI_HAMILTONIAN_DOC = BloqDocSpec( + bloq_cls=KikuchiHamiltonian, examples=[_kikuchi_matrix_symb, _kikuchi_matrix] +) diff --git a/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding_test.py b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding_test.py new file mode 100644 index 000000000..d056a0fb2 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kikuchi_block_encoding_test.py @@ -0,0 +1,59 @@ +# 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 pytest + +from qualtran.bloqs.basic_gates import Swap +from qualtran.resource_counting import get_cost_value, QECGatesCost + +from .kikuchi_block_encoding import _kikuchi_matrix, _kikuchi_matrix_symb + + +@pytest.mark.parametrize("bloq_ex", [_kikuchi_matrix, _kikuchi_matrix_symb]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +@pytest.mark.notebook +def test_notebook(): + from qualtran.testing import execute_notebook + + execute_notebook('kikuchi_block_encoding') + + +def test_controlled_cost(): + bloq = _kikuchi_matrix() + _, sigma = bloq.call_graph(max_depth=2) + _, ctrl_sigma = bloq.controlled().call_graph(max_depth=2) + + assert set(sigma.items()) - set(ctrl_sigma.items()) == {(Swap(32), 1), (Swap(1), 1)} + assert set(ctrl_sigma.items()) - set(sigma.items()) == { + (Swap(32).controlled(), 1), + (Swap(1).controlled(), 1), + } + + +def test_cost(): + bloq = _kikuchi_matrix() + + _ = get_cost_value(bloq, QECGatesCost()) + + +def test_cost_symb(): + bloq = _kikuchi_matrix_symb() + + _ = get_cost_value(bloq, QECGatesCost()) + print(_) diff --git a/qualtran/bloqs/max_k_xor_sat/kxor_instance.py b/qualtran/bloqs/max_k_xor_sat/kxor_instance.py new file mode 100644 index 000000000..913ae06ae --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kxor_instance.py @@ -0,0 +1,273 @@ +# 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. +# +# 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 itertools +from collections import defaultdict +from functools import cached_property +from typing import cast, Sequence, TypeAlias, Union + +import numpy as np +import sympy +from attrs import evolve, field, frozen +from numpy.typing import NDArray + +from qualtran.symbolics import bit_length, ceil, HasLength, is_symbolic, log2, slen, SymbolicInt + +Scope: TypeAlias = Union[tuple[int, ...], HasLength] +"""A subset of variables""" + + +def _sort_scope(S: Scope) -> Scope: + if is_symbolic(S): + return S + return tuple(sorted(S)) + + +@frozen +class Constraint: + """A single kXOR constraint. + + Definition 2.1. + + Note: n, k are not stored here, but only in the instance. + + Attributes: + S: the scope - subset of `[n]` of size k. + b: +1 or -1. + """ + + S: Scope = field(converter=_sort_scope) + b: SymbolicInt = field() + + @classmethod + def random(cls, n: int, k: int, *, rng: np.random.Generator): + """Single random constraint, Notation 2.3.""" + S = tuple(rng.choice(n, k, replace=False)) + b = rng.choice([-1, +1]) + return cls(S, b) + + @classmethod + def random_planted(cls, n: int, k: int, *, rho: float, z: NDArray, rng: np.random.Generator): + """Single planted constraint, Notation 2.4.""" + S = tuple(rng.choice(n, k, replace=False)) + eta = (-1) ** (rng.random() < (1 + rho) / 2) # i.e. expectation rho. + unplanted = cls(S, 1) # supporting constraint to evaluate z^S + b = eta * unplanted.evaluate(z) + return cls(S, b) + + @classmethod + def symbolic(cls, n: SymbolicInt, ix: int): + return cls(HasLength(n), sympy.Symbol(f"b_{ix}")) + + def is_symbolic(self): + return is_symbolic(self.S, self.b) + + def evaluate(self, x: NDArray[np.integer]): + return np.prod(x[np.array(self.S)]) + + +@frozen +class KXorInstance: + r"""A kXOR instance $\mathcal{I}$. + + Definition 2.1: A kXOR instance $\mathcal{I}$ over variables indexed by $[n]$ + consists of a multiset of constraints $\mathcal{C} = (S, b)$, where each scope + $S \subseteq [n]$ has cardinality $k$, and each right-hand side $b \in \{\pm 1\}$. + + Attributes: + n: number of variables. + k: number of variables per clause. + constraints: a tuple of `m` Constraints. + max_rhs: maximum value of the RHS polynomial $B_\mathcal{I}(S)$. + see default constructor for default value. In case the instance is symbolic, + the user can specify an expression for this, to avoid the default value. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Definition 2.1. + """ + + n: SymbolicInt + k: SymbolicInt + constraints: Union[tuple[Constraint, ...], HasLength] + max_rhs: SymbolicInt = field() + + @max_rhs.default + def _default_max_rhs(self): + """With very high probability, the max entry will be quite small. + + This is a classical preprocesing step. Time $m$. + """ + if is_symbolic(self.constraints) or is_symbolic(*self.constraints): + # user did not provide a value, assume some small constant + return 2 + + # instance is not symbolic, so we can compute the exact value. + assert isinstance(self.batched_scopes, tuple) + return max(abs(b) for _, b in self.batched_scopes) + + @cached_property + def m(self): + return slen(self.constraints) + + @classmethod + def random_instance( + cls, n: int, m: int, k: int, *, planted_advantage: float = 0, rng: np.random.Generator + ): + r"""Generate a random kXOR instance with the given planted advantage. + + `planted_advantage=0` generates random instances, and `1` generates a + linear system with a solution. + + Args: + n: number of variables + m: number of clauses + k: number of terms per clause + planted_advantage: $\rho$ + rng: random generator + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Notation 2.4. + """ + # planted vector + z = rng.choice([-1, +1], size=n) + + # constraints + constraints = tuple( + Constraint.random_planted(n=n, k=k, rho=planted_advantage, z=z, rng=rng) + for _ in range(m) + ) + + return cls(n=n, k=k, constraints=constraints) + + @classmethod + def symbolic(cls, n: SymbolicInt, m: SymbolicInt, k: SymbolicInt, *, max_rhs: SymbolicInt = 2): + """Create a symbolic instance with n variables, m constraints.""" + constraints = HasLength(m) + return cls(n=n, k=k, constraints=constraints, max_rhs=max_rhs) + + def is_symbolic(self): + if is_symbolic(self.n, self.m, self.k, self.constraints): + return True + assert isinstance(self.constraints, tuple) + return is_symbolic(*self.constraints) + + def subset(self, indices: Union[Sequence[int], HasLength]) -> 'KXorInstance': + """Pick a subset of clauses defined by the set of indices provided.""" + if self.is_symbolic() or is_symbolic(indices): + return evolve(self, constraints=HasLength(slen(indices))) + assert isinstance(self.constraints, tuple) + + constraints = tuple(self.constraints[i] for i in indices) + return evolve(self, constraints=constraints) + + @cached_property + def index_bitsize(self): + """number of bits required to represent the index of a variable, i.e. `[n]` + + We assume zero-indexing. + """ + return ceil(log2(self.n)) + + @cached_property + def num_unique_constraints(self) -> SymbolicInt: + return slen(self.batched_scopes) + + @cached_property + def batched_scopes(self) -> Union[tuple[tuple[Scope, int], ...], HasLength]: + r"""Group all the constraints by Scope, and add up the $b$ values. + + This is a classical preprocessing step. Time $k m \log m$. + """ + if self.is_symbolic(): + return HasLength(self.m) + + assert isinstance(self.constraints, tuple) + + batches: dict[Scope, int] = defaultdict(lambda: 0) + for con in self.constraints: + assert isinstance(con.S, tuple) + batches[con.S] += con.b + + batches_sorted = sorted(batches.items(), key=lambda c: c[1]) + return tuple(batches_sorted) + + @cached_property + def rhs_sum_bitsize(self): + r"""number of bits to represent the RHS polynomial $B_{\mathcal{I}}(S)$.""" + return bit_length(2 * self.max_rhs) + + def scope_as_int(self, S: Scope) -> int: + r"""Convert a scope into a single integer. + + Given a scope `S = (x_1, x_2, ..., x_k)`, and a bitsize `r` for each index, + the integer representation is given by concatenating `r`-bit unsigned repr + of each `x_i`. That is, $\sum_i r^{k - i} x_i$. + + This uses Big-endian representation, like all qualtran dtypes. + + The bitsize `r` is picked as `ceil(log(n))` for an n-variable instance. + """ + assert not is_symbolic(S) + + bitsize = self.index_bitsize + + result = 0 + for x in S: + result = (result << bitsize) + x + return result + + def brute_force_sparsity(self, ell: int) -> int: + r"""Compute the sparsity of the Kikuchi matrix with parameter $\ell$ by brute force. + + Takes time `O(C(n, l) * m * l)`. Extremely slow, use with caution. + """ + assert isinstance(self.n, int) + s = 0 + for S in itertools.combinations(range(self.n), ell): + nz = 0 + for U, _ in cast(tuple, self.batched_scopes): + T = set(S).symmetric_difference(U) + if len(T) == ell: + nz += 1 + s = max(s, nz) + return s + + +def example_kxor_instance() -> KXorInstance: + n, k = 10, 4 + cs = ( + Constraint((0, 1, 2, 3), -1), + Constraint((0, 2, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((0, 3, 4, 5), 1), + Constraint((1, 2, 3, 4), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((1, 3, 4, 5), -1), + Constraint((2, 3, 4, 5), 1), + ) + inst = KXorInstance(n, k, cs) + return inst diff --git a/qualtran/bloqs/max_k_xor_sat/kxor_instance_test.py b/qualtran/bloqs/max_k_xor_sat/kxor_instance_test.py new file mode 100644 index 000000000..700dfd6eb --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/kxor_instance_test.py @@ -0,0 +1,30 @@ +# 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 numpy as np +import pytest + +from .kxor_instance import KXorInstance + + +@pytest.mark.slow +@pytest.mark.parametrize("rho", [0, 0.8, 0.9]) +def test_max_rhs(rho: float): + rng = np.random.default_rng(402) + + rhs = [] + for i in range(100): + inst = KXorInstance.random_instance(n=100, m=1000, k=4, planted_advantage=rho, rng=rng) + rhs.append(inst.max_rhs) + + assert max(rhs) == 2 diff --git a/qualtran/bloqs/max_k_xor_sat/load_kxor_instance.ipynb b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance.ipynb new file mode 100644 index 000000000..21fe47dd3 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "448cdbc3", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# kXOR: Instance load Oracles\n", + "\n", + "We define three oracles that load a kXOR instance, which are used in the algorithm.\n", + "\n", + "We are given a kXOR instance $\\mathcal{I}$ of $n$ variables,\n", + "with $\\bar{m}$ unique scopes $\\{U_j | j \\in [\\bar{m}]\\}$.\n", + "We provide oracles to:\n", + "1. `LoadConstraintScopes`: Given $j \\in [\\bar{m}]$, compute $U_j$.\n", + "2. `LoadUniqueScopeIndex`: Given $U_j$, compute $j \\in [\\bar{m}]$\n", + "3. `PRGAUniqueConstraintRHS` Given $j$, apply $Rx(arccos(\\sqrt{B_\\mathcal{I}(S)/M}))$ on a target qubit.\n", + "(for an appropriate normalization $M$).\n", + "\n", + "\n", + "The first two oracles are independent of the RHS.\n", + "All these oracles can output arbitrary values for invalid inputs.\n", + "\n", + "References:\n", + " [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1)\n", + " Notation 2.24 for $B_\\mathcal{I}$.\n", + " Theorem 4.17, proof para 2 for $U_j$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "611e4ef6", + "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": "b5e118d0", + "metadata": { + "cq.autogen": "LoadConstraintScopes.bloq_doc.md" + }, + "source": [ + "## `LoadConstraintScopes`\n", + "Given an index $j$, load the scope of the $j$-th unique constraint.\n", + "\n", + "Given a $k$-XOR-SAT instance `inst` with $n$ variables and $m$ constraints.\n", + "Assuming `inst` has $\\bar{m}$ unique constraints, we define $U_j \\in {[n] \\choose k}$\n", + "for $j \\in [\\bar{m}]$ as the $j$-th unique constraint scope.\n", + "\n", + "The scopes are loaded using a QROM.\n", + "\n", + "If the input contains an invalid index, then any arbitrary value can be output.\n", + "\n", + "#### Registers\n", + " - `j`: a number in [\\bar{m}]\n", + " - `U`: $j$-th unique scope\n", + " - `ancilla`: entangled intermediate qubits, to be uncomputed by the adjoint. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). Theorem 4.17, proof para 2.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51cbd971", + "metadata": { + "cq.autogen": "LoadConstraintScopes.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import LoadConstraintScopes" + ] + }, + { + "cell_type": "markdown", + "id": "45f7354e", + "metadata": { + "cq.autogen": "LoadConstraintScopes.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137d0cb6", + "metadata": { + "cq.autogen": "LoadConstraintScopes.load_scopes_symb" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance\n", + "\n", + "n, m, k = sympy.symbols(\"n m k\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n=n, m=m, k=k)\n", + "load_scopes_symb = LoadConstraintScopes(inst)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1a93c7c", + "metadata": { + "cq.autogen": "LoadConstraintScopes.load_scopes" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance\n", + "\n", + "inst = KXorInstance(\n", + " n=6,\n", + " k=4,\n", + " constraints=(\n", + " Constraint(S=(0, 1, 2, 3), b=1),\n", + " Constraint(S=(0, 1, 4, 5), b=-1),\n", + " Constraint(S=(1, 2, 4, 5), b=1),\n", + " Constraint(S=(0, 3, 4, 5), b=1),\n", + " Constraint(S=(2, 3, 4, 5), b=1),\n", + " Constraint(S=(0, 1, 2, 3), b=1),\n", + " Constraint(S=(0, 3, 4, 5), b=1),\n", + " Constraint(S=(2, 3, 4, 5), b=1),\n", + " ),\n", + ")\n", + "load_scopes = LoadConstraintScopes(inst)" + ] + }, + { + "cell_type": "markdown", + "id": "107c977f", + "metadata": { + "cq.autogen": "LoadConstraintScopes.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a998692", + "metadata": { + "cq.autogen": "LoadConstraintScopes.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([load_scopes_symb, load_scopes],\n", + " ['`load_scopes_symb`', '`load_scopes`'])" + ] + }, + { + "cell_type": "markdown", + "id": "8b3ac93d", + "metadata": { + "cq.autogen": "LoadConstraintScopes.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29b2841a", + "metadata": { + "cq.autogen": "LoadConstraintScopes.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "load_scopes_symb_g, load_scopes_symb_sigma = load_scopes_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(load_scopes_symb_g)\n", + "show_counts_sigma(load_scopes_symb_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/max_k_xor_sat/load_kxor_instance.py b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance.py new file mode 100644 index 000000000..0ea01c663 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance.py @@ -0,0 +1,356 @@ +# 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. +r"""We define three oracles that load a kXOR instance, which are used in the algorithm. + +We are given a kXOR instance $\mathcal{I}$ of $n$ variables, +with $\bar{m}$ unique scopes $\{U_j | j \in [\bar{m}]\}$. +We provide oracles to: +1. `LoadConstraintScopes`: Given $j \in [\bar{m}]$, compute $U_j$. +2. `LoadUniqueScopeIndex`: Given $U_j$, compute $j \in [\bar{m}]$ +3. `PRGAUniqueConstraintRHS` Given $j$, apply $Rx(arccos(\sqrt{B_\mathcal{I}(S)/M}))$ on a target qubit. +(for an appropriate normalization $M$). + + +The first two oracles are independent of the RHS. +All these oracles can output arbitrary values for invalid inputs. + +References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Notation 2.24 for $B_\mathcal{I}$. + Theorem 4.17, proof para 2 for $U_j$. +""" +from functools import cached_property +from typing import Counter, Optional, Sequence, Union + +import numpy as np +from attrs import evolve, frozen + +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + BQUInt, + CtrlSpec, + DecomposeTypeError, + QAny, + QBit, + QFxp, + Register, + Side, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic import EqualsAConstant, LessThanConstant +from qualtran.bloqs.basic_gates import Hadamard, SGate +from qualtran.bloqs.bookkeeping import Partition +from qualtran.bloqs.data_loading import QROM +from qualtran.bloqs.rotations.rz_via_phase_gradient import RzViaPhaseGradient +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ceil, HasLength, is_symbolic, is_zero, log2, SymbolicInt + +from .kxor_instance import KXorInstance + + +@frozen +class LoadConstraintScopes(Bloq): + r"""Given an index $j$, load the scope of the $j$-th unique constraint. + + Given a $k$-XOR-SAT instance `inst` with $n$ variables and $m$ constraints. + Assuming `inst` has $\bar{m}$ unique constraints, we define $U_j \in {[n] \choose k}$ + for $j \in [\bar{m}]$ as the $j$-th unique constraint scope. + + The scopes are loaded using a QROM. + + If the input contains an invalid index, then any arbitrary value can be output. + + Registers: + j: a number in [\bar{m}] + U (RIGHT): $j$-th unique scope + ancilla (RIGHT): entangled intermediate qubits, to be uncomputed by the adjoint. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Theorem 4.17, proof para 2. + """ + + inst: KXorInstance + + @cached_property + def signature(self) -> 'Signature': + registers: list[Register] = [ + Register('j', self.m_dtype), + Register('U', QAny(self.scope_bitsize), side=Side.RIGHT), + ] + + if not is_zero(self.ancilla_bitsize): + registers.append(Register('ancilla', QAny(self.ancilla_bitsize), side=Side.RIGHT)) + + return Signature(registers) + + @cached_property + def scope_bitsize(self) -> SymbolicInt: + """total number of bits to store `k` indices in `[n]`.""" + return self.inst.k * self.inst.index_bitsize + + @cached_property + def ancilla_bitsize(self) -> SymbolicInt: + """ancillas used by the underlying QRO(A)M""" + return 0 + + @cached_property + def m_dtype(self): + r"""number of bits to store $j \in [\bar{m}]$.""" + m = self.inst.num_unique_constraints + bitsize = ceil(log2(m)) + return BQUInt(bitsize, m) + + @cached_property + def _qrom_bloq(self) -> QROM: + # TODO use QROAMClean? + + if self.inst.is_symbolic(): + return QROM.build_from_bitsize(self.inst.num_unique_constraints, self.scope_bitsize) + + assert isinstance(self.inst.batched_scopes, tuple) + scopes = np.array([S for S, _ in self.inst.batched_scopes], dtype=int) + assert scopes.shape == (self.inst.num_unique_constraints, self.inst.k) + return QROM.build_from_data( + *scopes.T, target_bitsizes=(self.inst.index_bitsize,) * self.inst.k + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', j: 'Soquet') -> dict[str, 'SoquetT']: + if self.inst.is_symbolic(): + raise DecomposeTypeError(f"cannot decompose symbolic {self}") + + targets = { + f'target{i}_': bb.allocate(self.inst.index_bitsize) for i in range(int(self.inst.k)) + } + targets = bb.add_d(self._qrom_bloq, selection=j, **targets) + j = targets.pop('selection') + + U = bb.add( + Partition(self.scope_bitsize, self._qrom_bloq.target_registers).adjoint(), **targets + ) + return {'j': j, 'U': U} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return {self._qrom_bloq: 1} + + +@bloq_example +def _load_scopes() -> LoadConstraintScopes: + from qualtran.bloqs.max_k_xor_sat.kxor_instance import Constraint, KXorInstance + + inst = KXorInstance( + n=6, + k=4, + constraints=( + Constraint(S=(0, 1, 2, 3), b=1), + Constraint(S=(0, 1, 4, 5), b=-1), + Constraint(S=(1, 2, 4, 5), b=1), + Constraint(S=(0, 3, 4, 5), b=1), + Constraint(S=(2, 3, 4, 5), b=1), + Constraint(S=(0, 1, 2, 3), b=1), + Constraint(S=(0, 3, 4, 5), b=1), + Constraint(S=(2, 3, 4, 5), b=1), + ), + ) + load_scopes = LoadConstraintScopes(inst) + return load_scopes + + +@bloq_example +def _load_scopes_symb() -> LoadConstraintScopes: + import sympy + + from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance + + n, m, k = sympy.symbols("n m k", positive=True, integer=True) + inst = KXorInstance.symbolic(n=n, m=m, k=k) + load_scopes_symb = LoadConstraintScopes(inst) + return load_scopes_symb + + +_LOAD_INSTANCE_DOC = BloqDocSpec( + bloq_cls=LoadConstraintScopes, examples=[_load_scopes_symb, _load_scopes] +) + + +@frozen +class LoadUniqueScopeIndex(Bloq): + r"""Given a scope $S$, load $j$ such that $S = U_j$, the $j$-th unique scope. + + If the input contains an invalid scope, then any arbitrary value can be output. + + Registers: + S: A scope $S \in {[n] \choose k}$. + j (RIGHT): a number in $[\bar{m}]$ s.t. $S = U_j$. + ancilla (RIGHT): entangled intermediate qubits, to be uncomputed by the adjoint. + """ + + inst: KXorInstance + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(j=self.m_dtype, U=QAny(self.scope_bitsize)) + + @cached_property + def scope_bitsize(self) -> SymbolicInt: + """total number of bits to store `k` indices in `[n]`.""" + return self.inst.k * self.inst.index_bitsize + + @cached_property + def m_dtype(self): + r"""number of bits to store $j \in [\bar{m}]$.""" + m = self.inst.num_unique_constraints + bitsize = ceil(log2(m)) + return BQUInt(bitsize, m) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + counts = Counter[Bloq]() + + c = ssa.new_symbol("c") + counts[EqualsAConstant(self.scope_bitsize, c)] += self.inst.num_unique_constraints + + return counts + + +@frozen +class PRGAUniqueConstraintRHS(Bloq): + r"""Map $|j\rangle |0\rangle$ to $|j\rangle (\sqrt{E_j} |0\rangle + \sqrt{1 - |E_j|}|1\rangle)$ + + Given an instance $\mathcal{I}$, with unique scopes $U_j$ and corresponding RHS values + $E_j = B_\mathcal{I}(U_j)/M$ (where $M$ is the max. abs. entry, usually 2) + apply the above rotation on the target qubit. + + This is done by first rotating for $|E_j|$ (i.e. ignoring the sign), + by loading the values $\arccos{\sqrt{|E_j|}} / (2 * \pi)$, + and applying an `Rx` using an `RzViaPhaseGradient` surrounded by `H`. + + We then apply the sign correction of $i$ for the negative entries by an $S$ gate. + We ensure that the input data is sorted, therefore we can simply compare $j$ + with the largest negative index, and apply a `CS` gate. + + Args: + inst: kXOR instance $\mathcal{I}$. + angle_bitsize: number of bits to load the amplitude rotation angles to. + + Registers: + j: Selection index, loads the value of $E_j = B_\mathcal{I}(U_j)/M$ + q: rotation target. + """ + + inst: KXorInstance + angle_bitsize: SymbolicInt + cv: Optional[int] = None + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(ctrl=QAny(self.n_ctrl), j=self.m_dtype, q=QBit()) + + @property + def n_ctrl(self) -> int: + return 1 if self.cv is not None else 0 + + @cached_property + def m_dtype(self): + r"""number of bits to store $j \in [\bar{m}]$.""" + m = self.inst.num_unique_constraints + bitsize = ceil(log2(m)) + return BQUInt(bitsize, m) + + @cached_property + def _angle_dtype(self): + return QFxp(self.angle_bitsize, self.angle_bitsize) + + @cached_property + def _qrom_angle_data( + self, + ) -> tuple[Union[HasLength, Sequence[int]], Union[HasLength, Sequence[int]]]: + M = self.inst.max_rhs + scopes = self.inst.batched_scopes + if is_symbolic(M) or is_symbolic(scopes): + m = self.inst.num_unique_constraints + return HasLength(m), HasLength(m) + + b = [b for _, b in scopes] + assert np.all(b == np.sort(b)), "data must be sorted!" + + amplitude_angles = np.arccos(np.sqrt(np.abs(b) / M)) + amplitude_angles_int = np.round(amplitude_angles * 2**self.angle_bitsize) + + signs = tuple(np.sign(b)) + return amplitude_angles_int, signs + + @cached_property + def _amplitude_qrom(self) -> QROM: + data, _ = self._qrom_angle_data + if is_symbolic(data): + return QROM.build_from_bitsize( + data_len_or_shape=self.inst.num_unique_constraints, + target_bitsizes=self.angle_bitsize, + num_controls=self.n_ctrl, + ) + + return QROM.build_from_data( + data, target_bitsizes=(self.angle_bitsize,), num_controls=self.n_ctrl + ) + + @cached_property + def _num_negative(self) -> SymbolicInt: + """returns $k$ s.t. the first $k$ elements are negative.""" + _, signs = self._qrom_angle_data + if is_symbolic(signs): + return self.inst.num_unique_constraints // 2 + + assert np.all(signs == np.sort(signs)), "data must be sorted!" + return int(np.searchsorted(signs, 0)) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + counts = Counter[Bloq]() + + # load the amplitudes + counts[self._amplitude_qrom] += 1 + + # apply a Rx rotation using Rx = H Rz H + counts[Hadamard()] += 2 + counts[RzViaPhaseGradient(self._angle_dtype, self._angle_dtype)] += 1 + + # apply the sign correction + # TODO use the half-bloq once implemented to wire this correctly + sign_compare = LessThanConstant(self.m_dtype.num_qubits, self._num_negative) + counts[sign_compare] += 1 + counts[SGate().controlled()] += 1 + + # unload amplitudes + counts[self._amplitude_qrom.adjoint()] += 1 + + return counts + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']: + from qualtran.bloqs.mcmt.bloq_with_specialized_single_qubit_control import ( + get_ctrl_system_for_bloq_with_specialized_single_qubit_control, + ) + + return get_ctrl_system_for_bloq_with_specialized_single_qubit_control(self, ctrl_spec) + + def with_cv(self, *, cv: Optional[int]) -> 'Bloq': + return evolve(self, cv=cv) + + @property + def ctrl_reg_name(self) -> str: + return 'ctrl' diff --git a/qualtran/bloqs/max_k_xor_sat/load_kxor_instance_test.py b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance_test.py new file mode 100644 index 000000000..97f833b45 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/load_kxor_instance_test.py @@ -0,0 +1,49 @@ +# 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 unittest.mock import ANY + +import pytest + +from qualtran import Bloq +from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost + +from .load_kxor_instance import _load_scopes, _load_scopes_symb + + +@pytest.mark.parametrize("bloq", [_load_scopes, _load_scopes_symb]) +def test_examples(bloq_autotester, bloq: Bloq): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq) + + +def test_load_instance(): + bloq = _load_scopes() + + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts(and_bloq=3, clifford=ANY, measurement=ANY) + + # classical action + for j, (S, _) in enumerate(tuple(bloq.inst.batched_scopes)): # type: ignore + assert bloq.call_classically(j=j) == (j, bloq.inst.scope_as_int(S)) + + +def test_load_instance_cost_symb(): + bloq = _load_scopes_symb() + + m, k = bloq.inst.m, bloq.inst.k + logn = bloq.inst.index_bitsize + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts(and_bloq=m - 2, clifford=k * m * logn + m - 2, measurement=m - 2) diff --git a/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.ipynb b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.ipynb new file mode 100644 index 000000000..d42537de5 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ee412c69", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Algorithm: Planted Noise kXOR" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df5a7866", + "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": "36559e8d", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.bloq_doc.md" + }, + "source": [ + "## `PlantedNoisyKXOR`\n", + "Algorithm for Planted Noisy kXOR.\n", + "\n", + "Problem (Problem 2.6 of Ref [1]):\n", + "\n", + "Given a noisy-kXOR instance $\\hat{\\mathcal{I}}$ which is drawn either:\n", + "\n", + "1. with planted advantage $\\rho$, from $\\tilde\\mathcal{P}^{z}_{n, k}(m, \\rho)$.\n", + "2. at random, from $\\tilde\\mathcal{R}_{n, k}(m)$.\n", + "\n", + "output a single bit such that it is whp `1` in case 1, and `0` in case 2.\n", + "\n", + "Algorithm (Section 4.4, Theorem 4.18):\n", + "We first split the instance into $\\hat{\\mathcal{I}} = \\mathcal{I} \\cup \\mathcal{I}_\\text{guide}$,\n", + "by placing each constraint independently in $\\mathcal{I}$ with prob. $1 - \\zeta$,\n", + "otherwise in $\\mathcal{I}_\\text{guide}$.\n", + "$\\zeta$ is picked to be $1 / \\ln n$.\n", + "\n", + "#### Parameters\n", + " - `inst_guide`: The subset of contraints $\\mathcal{I}_\\text{guide}$ for the guided state.\n", + " - `inst_solve`: The subset of constraints $\\mathcal{I}$ for eigenvalue estimation.\n", + " - `ell`: Kikuchi parameter $\\ell$.\n", + " - `rho`: the planted advantage $\\rho$ in the planted case. \n", + "\n", + "#### References\n", + " - [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1). \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f930da5f", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import PlantedNoisyKXOR" + ] + }, + { + "cell_type": "markdown", + "id": "18f43abd", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a28aa9c", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.solve_planted_symbolic" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KXorInstance\n", + "from qualtran.symbolics import HasLength\n", + "\n", + "n, m = sympy.symbols(\"n m\", positive=True, integer=True)\n", + "k = sympy.symbols(\"k\", positive=True, integer=True, even=True)\n", + "c = sympy.symbols(\"c\", positive=True, integer=True)\n", + "ell = c * k\n", + "rho = sympy.Symbol(r\"\\rho\", positive=True, real=True)\n", + "\n", + "inst = KXorInstance.symbolic(n, m, k)\n", + "zeta = 1 / ln(n)\n", + "solve_planted_symbolic = PlantedNoisyKXOR(\n", + " inst_guide=inst.subset(HasLength((1 - zeta) * m)),\n", + " inst_solve=inst.subset(HasLength((zeta) * m)),\n", + " ell=ell,\n", + " rho=rho,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2df9b4ea", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.solve_planted" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KXorInstance\n", + "\n", + "rng = np.random.default_rng(42)\n", + "n, m, k = 50, 1000, 4\n", + "ell = k\n", + "rho = 0.8\n", + "\n", + "inst = KXorInstance.random_instance(n, m, k, planted_advantage=rho, rng=rng)\n", + "solve_planted = PlantedNoisyKXOR.from_inst(inst, ell=ell, rho=rho, zeta=0.1, rng=rng)" + ] + }, + { + "cell_type": "markdown", + "id": "505c1e9c", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ead66fe", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([solve_planted_symbolic, solve_planted],\n", + " ['`solve_planted_symbolic`', '`solve_planted`'])" + ] + }, + { + "cell_type": "markdown", + "id": "bd068fb3", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "946fca15", + "metadata": { + "cq.autogen": "PlantedNoisyKXOR.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "solve_planted_symbolic_g, solve_planted_symbolic_sigma = solve_planted_symbolic.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(solve_planted_symbolic_g)\n", + "show_counts_sigma(solve_planted_symbolic_sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "2c8e9045-060a-4386-99be-5af7ee653fd6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "#### Counts totals:\n", + " - `Adjoint(subbloq=GuidedHamiltonianPhaseEstimation)`: $\\displaystyle \\left\\lceil{\\frac{202.020202020202 c^{0.5} k^{0.5} \\left(c k\\right)^{\\frac{c k}{2}}}{Part_{k}(\\ell)^{0.5} \\rho^{0.5} \\left(\\frac{\\left(\\frac{m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}}{{\\binom{n}{k}}}\\right)^{c} \\left(\\frac{\\rho^{2} m}{\\left(m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) \\operatorname{log}_{2}{\\left(n \\right)}}\\right)^{c}}{\\log{\\left(n \\right)}^{2}}\\right)^{0.5}}}\\right\\rceil$\n", + " - `GuidedHamiltonianPhaseEstimation`: $\\displaystyle \\left\\lceil{\\frac{202.020202020202 c^{0.5} k^{0.5} \\left(c k\\right)^{\\frac{c k}{2}}}{Part_{k}(\\ell)^{0.5} \\rho^{0.5} \\left(\\frac{\\left(\\frac{m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}}{{\\binom{n}{k}}}\\right)^{c} \\left(\\frac{\\rho^{2} m}{\\left(m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) \\operatorname{log}_{2}{\\left(n \\right)}}\\right)^{c}}{\\log{\\left(n \\right)}^{2}}\\right)^{0.5}}}\\right\\rceil + 1$\n", + " - `MultiControlZ`: $\\displaystyle \\left\\lceil{\\frac{202.020202020202 c^{0.5} k^{0.5} \\left(c k\\right)^{\\frac{c k}{2}}}{Part_{k}(\\ell)^{0.5} \\rho^{0.5} \\left(\\frac{\\left(\\frac{m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}}{{\\binom{n}{k}}}\\right)^{c} \\left(\\frac{\\rho^{2} m}{\\left(m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) \\operatorname{log}_{2}{\\left(n \\right)}}\\right)^{c}}{\\log{\\left(n \\right)}^{2}}\\right)^{0.5}}}\\right\\rceil$\n", + " - `ZGate`: $\\displaystyle \\left\\lceil{\\frac{202.020202020202 c^{0.5} k^{0.5} \\left(c k\\right)^{\\frac{c k}{2}}}{Part_{k}(\\ell)^{0.5} \\rho^{0.5} \\left(\\frac{\\left(\\frac{m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}}{{\\binom{n}{k}}}\\right)^{c} \\left(\\frac{\\rho^{2} m}{\\left(m \\left(1 - \\frac{1}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) + \\frac{m}{\\operatorname{log}_{2}{\\left(n \\right)}}\\right) \\operatorname{log}_{2}{\\left(n \\right)}}\\right)^{c}}{\\log{\\left(n \\right)}^{2}}\\right)^{0.5}}}\\right\\rceil$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "_, sigma = solve_planted_symbolic.call_graph(max_depth=2, generalizer=ignore_split_join)\n", + "show_counts_sigma(sigma) # inverse of Eq. 150" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.py b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.py new file mode 100644 index 000000000..3dd4078b4 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor.py @@ -0,0 +1,424 @@ +# 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 functools import cached_property +from typing import Optional + +import numpy as np +import scipy +import sympy +from attrs import field, frozen + +from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, Signature, SoquetT +from qualtran.bloqs.state_preparation.black_box_prepare import BlackBoxPrepare +from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import ( + ceil, + HasLength, + is_symbolic, + ln, + log2, + prod, + slen, + ssqrt, + SymbolicFloat, + SymbolicInt, +) + +from .guided_hamiltonian import GuidedHamiltonian +from .guiding_state import GuidingState, SimpleGuidingState +from .kikuchi_block_encoding import KikuchiHamiltonian +from .kxor_instance import KXorInstance + + +def comb(n: SymbolicInt, k: SymbolicInt) -> SymbolicInt: + """compute n choose k""" + if is_symbolic(n, k): + return sympy.binomial(n, k) + return scipy.special.comb(n, k) + + +@frozen(kw_only=True) +class KikuchiAverageDegreeTheorem: + """Compute the average degree of the Kikuchi matrix. + + The Alice theorem (Thm 2.21) guarantees this with high probability + for random/planted instances. + """ + + n: SymbolicInt + k: SymbolicInt + ell: SymbolicInt + + @cached_property + def delta(self) -> SymbolicFloat: + """Eq 19""" + n, k, l = self.n, self.k, self.ell + term_1 = comb(k, k // 2) + term_2 = comb(n - k, l - k // 2) / comb(n, l) + return term_1 * term_2 + + +@frozen(kw_only=True) +class AliceTheorem: + r"""Alice theorem, E.g. Theorem 2.21. + + Consider a $k$XOR instance over $n$ variables and $m$ clauses, with Kikuchi parameter $\ell$. + + Assume: + - $\ell \ge k/2$ + - $n \gg k \ell$ + + For any parameters $\kappa \le 1$ and $0 < \epsilon \le \kappa/(2+\kappa)$, + assume: $m$ satisfies + $m/n \ge C_\kappa (n/\ell)^{(k-2)/2}$ + where + $C_\kappa = 2(1+\epsilon)(1+\kappa) \kappa^{-2} {k \choose k/2}^{-1} \ln n$ + + Then for a randomly drawn instance $\mathcal{I}$ (i.e. advantage 0), + except with probability $3 n^{-\epsilon \ell}$, we are guaranteed: + + $\lambda_\max{\mathcal{K}_\ell(\mathcal{I})} \le \kappa d$ + where + $d = \delta_{\ell,n,k} m$. + + Args: + n: number of variables $n$ + k: number of variables per equation $k$ + ell: Kikuchi parameter + kappa: parameter $\kappa$ + eps: parameter $\epsilon$ + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + """ + + n: int + k: int + ell: int + kappa: float + eps: float = field() + + @eps.default + def _default_max_eps(self): + return self.kappa / (2 + self.kappa) + + def __attrs_post_init__(self): + assert self.k % 2 == 0, "k must be even" + assert self.ell % self.k == 0, "l must be a multiple of k" + # assert self.n >= self.k * self.ell, "n must be atleast lk" + assert 0 <= self.kappa <= 1 + assert 0 < self.eps <= self.kappa / (2 + self.kappa) + + @cached_property + def C_kappa(self): + """Eq 20 (right)""" + term_1 = 2 * (1 + self.eps) * (1 + self.kappa) / self.kappa**2 + term_2 = comb(self.k, self.k // 2) + term_3 = np.log(self.n) + + value = (term_1 / term_2) * term_3 + return value + + @cached_property + def fail_prob(self): + return 3 / self.n ** (self.eps * self.ell) + + @cached_property + def min_m(self): + """Eq 20 (left)""" + m = self.C_kappa * (self.n / self.ell) ** (self.k // 2) * self.ell + return ceil(m) + + +@frozen(kw_only=True) +class GuidingStateOverlapTheorem: + r"""Lower-bound the overlap of the prepared guiding state with the eigenspace. + + This is an implementation of Theorem 2.40. + + Args: + n: number of variables + k: number of variables per constraint + m_hat: total number of constraints + ell: kikuchi parameter $\ell$ + zeta: the probability of picking a constraint for $\mathcal{I}_\text{guide}$. + nu: parameter in $(0, .99]$. + eps: parameter. + rho: planted advantage. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 2.7, Theorem 2.40. + """ + + n: SymbolicInt + k: SymbolicInt + m_hat: SymbolicInt + ell: SymbolicInt + zeta: SymbolicFloat + nu: SymbolicFloat + eps: SymbolicFloat + rho: SymbolicFloat + + @cached_property + def part_k_l(self) -> SymbolicInt: + ell, k = self.ell, self.k + if is_symbolic(ell) or is_symbolic(k): + return sympy.Symbol(r"Part_{k}(\ell)", positive=True, integer=True) + return prod([comb(ell - i * k, k) for i in range(ell // k)]) + + @cached_property + def xi(self): + r"""Eq 60 $\xi$""" + term_1 = self.part_k_l + term_2 = (self.rho * self.eps * self.nu) / (200 * self.ell * ln(self.n)) + term_3 = (self.rho**2 * self.zeta) ** (self.ell // self.k) + return term_1 * term_2 * term_3 + + @cached_property + def overlap_probability(self) -> SymbolicFloat: + term_2_base = self.m_hat / comb(self.n, self.k) + term_2 = term_2_base ** (self.ell / self.k) + return self.xi * term_2 + + +@frozen +class PlantedNoisyKXOR(Bloq): + r"""Algorithm for Planted Noisy kXOR. + + Problem (Problem 2.6 of Ref [1]): + + Given a noisy-kXOR instance $\hat{\mathcal{I}}$ which is drawn either: + + 1. with planted advantage $\rho$, from $\tilde\mathcal{P}^{z}_{n, k}(m, \rho)$. + 2. at random, from $\tilde\mathcal{R}_{n, k}(m)$. + + output a single bit such that it is whp `1` in case 1, and `0` in case 2. + + Algorithm (Section 4.4, Theorem 4.18): + We first split the instance into $\hat{\mathcal{I}} = \mathcal{I} \cup \mathcal{I}_\text{guide}$, + by placing each constraint independently in $\mathcal{I}$ with prob. $1 - \zeta$, + otherwise in $\mathcal{I}_\text{guide}$. + $\zeta$ is picked to be $1 / \ln n$. + + Args: + inst_guide: The subset of contraints $\mathcal{I}_\text{guide}$ for the guided state. + inst_solve: The subset of constraints $\mathcal{I}$ for eigenvalue estimation. + ell: Kikuchi parameter $\ell$. + rho: the planted advantage $\rho$ in the planted case. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + """ + + inst_guide: KXorInstance + inst_solve: KXorInstance + ell: SymbolicInt + rho: SymbolicFloat + _guiding_state_overlap: Optional[SymbolicFloat] = field(kw_only=True, default=None) + + def __attrs_post_init__(self): + k = self.inst_guide.k + if not is_symbolic(k): + assert k % 2 == 0, f"{k=} must be even" + + ell = self.ell + if not is_symbolic(k, ell): + assert ell % k == 0 and ell >= k, f"{ell=} must be a multiple of {k=}" + + @cached_property + def signature(self) -> 'Signature': + return self.guided_hamiltonian_bloq.signature + + @classmethod + def from_inst( + cls, + inst: KXorInstance, + ell: SymbolicInt, + rho: SymbolicFloat, + *, + rng: np.random.Generator, + zeta: Optional[SymbolicFloat] = None, + guiding_state_overlap: Optional[SymbolicFloat] = None, + ): + if zeta is None: + zeta = 1 / log2(inst.n) + + (use_for_guide,) = np.nonzero(np.atleast_1d(rng.random(inst.m) < zeta)) + inst_guide = inst.subset(tuple(use_for_guide)) + + if is_symbolic(inst, use_for_guide): + inst_solve = inst.subset(HasLength(inst.m - slen(use_for_guide))) + else: + mask = np.ones(inst.m) + mask[np.array(use_for_guide)] = 0 + (rest,) = np.nonzero(mask) + inst_solve = inst.subset(tuple(rest)) + + return cls( + inst_guide=inst_guide, + inst_solve=inst_solve, + ell=ell, + rho=rho, + guiding_state_overlap=guiding_state_overlap, + ) + + @cached_property + def guiding_state_and_coefficient(self) -> tuple[PrepareOracle, SymbolicFloat]: + r"""Return a bloq that prepares the guiding state, and its coefficient. + + If the bloq prepares $\beta |\Psi\rangle|0\rangle + |\perp\rangle|1\rangle$, + then this will return $|\beta|$. + + The returned $\beta$ is an theoretical lower bound on the true value, + and is correct for $1 - o(1)$ fraction of random instances. + """ + if self.ell == self.inst_guide.k: + return SimpleGuidingState(inst=self.inst_guide), 1 + bloq = GuidingState(inst=self.inst_guide, ell=self.ell) + return bloq, bloq.amplitude_good_part + + @cached_property + def guiding_state_overlap_guarantee(self) -> GuidingStateOverlapTheorem: + """Invoke Theorem 2.40 to obtain a lower bound on the guiding state overlap. + + The below parameters are picked from Theorem 4.18, proof para 2. + """ + n, k = self.inst_guide.n, self.inst_guide.k + m_guide = self.inst_guide.m + m_solve = self.inst_solve.m + m_hat = m_guide + m_solve + zeta = m_solve / m_hat + return GuidingStateOverlapTheorem( + n=n, k=k, ell=self.ell, m_hat=m_hat, zeta=zeta, nu=1 / ln(n), eps=0.005, rho=self.rho + ) + + @cached_property + def guiding_state_overlap(self) -> SymbolicFloat: + if self._guiding_state_overlap is not None: + return self.guiding_state_overlap + _, guiding_state_good_coeff = self.guiding_state_and_coefficient + return guiding_state_good_coeff + + @cached_property + def overlap(self) -> SymbolicFloat: + # guiding state + guiding_state_good_coeff = self.guiding_state_overlap + + # overlap of |\Gamma(A)> with the threshold eigenspace + overlap_good_eigen = self.guiding_state_overlap_guarantee.overlap_probability**0.5 + + # total overlap is the sqrt probability of the ancilla being 0, + # and the state being in the >= lambda eigenspace. + overlap = guiding_state_good_coeff * overlap_good_eigen + + return overlap + + @cached_property + def degree_guarantee(self) -> KikuchiAverageDegreeTheorem: + return KikuchiAverageDegreeTheorem(n=self.inst_solve.n, k=self.inst_solve.k, ell=self.ell) + + @cached_property + def sparsity(self) -> SymbolicInt: + """sparsity of the kikuchi matrix, $d$""" + # d = \delta m + d = self.degree_guarantee.delta * self.inst_solve.m + if is_symbolic(d): + return d # type: ignore + return ceil(d) + + @cached_property + def hamiltonian(self) -> KikuchiHamiltonian: + return KikuchiHamiltonian( + inst=self.inst_solve, ell=self.ell, entry_bitsize=10, s=self.sparsity + ) + + @cached_property + def guided_hamiltonian_bloq(self) -> GuidedHamiltonian: + # Thm 4.18 proof para 2. + # lambda = 0.995 rho d + eigenvalue_threshold = 0.995 * self.rho * self.sparsity + + kappa = 0.99 * self.rho + eps = 0.005 + + # Thm 4.18 proof para 3 + # kappa' <= (1 - alpha) lambda + # ==> alpha <= 1 - kappa'/lambda + # we pick kappa' s.t. it satisfies the alice theorem for inst_solve.m + # simple approximation: kappa' = kappa / sqrt(1-zeta) + zeta = self.inst_guide.m / (self.inst_guide.m + self.inst_solve.m) + kappa_prime = kappa / ssqrt(1 - zeta) + alpha = 1 - kappa_prime / eigenvalue_threshold + if not is_symbolic(alpha): + assert alpha > 0, f"got negative {alpha=}" + + guiding_state, _ = self.guiding_state_and_coefficient + + return GuidedHamiltonian( + self.hamiltonian, + BlackBoxPrepare(guiding_state), + lambd=eigenvalue_threshold, + alpha=alpha, + gamma=self.overlap, + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: + soqs = bb.add_d(self.guided_hamiltonian_bloq, **soqs) + return soqs + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return {self.guided_hamiltonian_bloq: 1} + + +@bloq_example +def _solve_planted() -> PlantedNoisyKXOR: + from qualtran.bloqs.max_k_xor_sat import KXorInstance + + rng = np.random.default_rng(42) + n, m, k = 50, 1000, 4 + ell = k + rho = 0.8 + + inst = KXorInstance.random_instance(n, m, k, planted_advantage=rho, rng=rng) + solve_planted = PlantedNoisyKXOR.from_inst(inst, ell=ell, rho=rho, zeta=0.1, rng=rng) + return solve_planted + + +@bloq_example +def _solve_planted_symbolic() -> PlantedNoisyKXOR: + from qualtran.bloqs.max_k_xor_sat import KXorInstance + from qualtran.symbolics import HasLength + + n, m = sympy.symbols("n m", positive=True, integer=True) + k = sympy.symbols("k", positive=True, integer=True, even=True) + c = sympy.symbols("c", positive=True, integer=True) + ell = c * k + rho = sympy.Symbol(r"\rho", positive=True, real=True) + + inst = KXorInstance.symbolic(n, m, k) + zeta = 1 / ln(n) + solve_planted_symbolic = PlantedNoisyKXOR( + inst_guide=inst.subset(HasLength((1 - zeta) * m)), + inst_solve=inst.subset(HasLength((zeta) * m)), + ell=ell, + rho=rho, + ) + return solve_planted_symbolic + + +_PLANTED_NOISY_KXOR_DOC = BloqDocSpec( + bloq_cls=PlantedNoisyKXOR, examples=[_solve_planted_symbolic, _solve_planted] +) diff --git a/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor_test.py b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor_test.py new file mode 100644 index 000000000..85b164294 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/planted_noisy_kxor_test.py @@ -0,0 +1,112 @@ +# 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 numpy as np +import pytest +import sympy + +from qualtran.drawing import show_call_graph +from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join +from qualtran.symbolics import ln, log2 + +from .kxor_instance import KXorInstance +from .planted_noisy_kxor import ( + _solve_planted, + _solve_planted_symbolic, + AliceTheorem, + PlantedNoisyKXOR, +) + + +@pytest.fixture +def rng(): + return np.random.default_rng(42) + + +@pytest.mark.xfail +def test_alice_thm_symb(): + n, m = sympy.symbols("n m", positive=True, integer=True) + k = sympy.symbols("k", positive=True, integer=True, even=True) + rho = sympy.symbols(r"\rho", positive=True, real=True) + c = sympy.symbols(r"c", positive=True, integer=True) + thm = AliceTheorem(n=n, k=k, ell=c * k, kappa=0.99 * rho, eps=0.005) + _ = thm.C_kappa() + _ = thm.min_m() + _ = thm.fail_prob() + + +@pytest.mark.parametrize("bloq_ex", [_solve_planted, _solve_planted_symbolic]) +def test_examples(bloq_autotester, bloq_ex): + if bloq_autotester.check_name == 'serialize': + pytest.skip() + + bloq_autotester(bloq_ex) + + +def test_call_graph(): + _solve_planted().call_graph() + + +def test_call_graph_symb(): + algo = _solve_planted_symbolic() + g, sigma = algo.call_graph(generalizer=[ignore_split_join, ignore_alloc_free]) + show_call_graph(g) + + +def example_random_instance(*, k=4, n=100, m=1000, c=2, rho=0.8, seed=120) -> PlantedNoisyKXOR: + # generate instance + rng = np.random.default_rng(seed) + ell = c * k + inst = KXorInstance.random_instance(n=n, m=m, k=k, planted_advantage=rho, rng=rng) + algo_bloq = PlantedNoisyKXOR.from_inst(inst=inst, ell=ell, rho=rho, zeta=1 / ln(n), rng=rng) + + return algo_bloq + + +def test_gate_cost(): + bloq = example_random_instance() + gc = get_cost_value(bloq, QECGatesCost()) + t_cost = gc.total_t_count(ts_per_cswap=4) + + n = bloq.inst_guide.n + m = bloq.inst_guide.m + bloq.inst_solve.m + ell = bloq.ell + c = ell // bloq.inst_guide.k + + big_O_expected = n ** (ell / 4) * (m**0.5) * ell**ell * log2(n) ** (c // 2) + print() + print(t_cost) + print(t_cost / big_O_expected) + print(big_O_expected) + print(t_cost / big_O_expected * bloq.guiding_state_overlap) + print(1 / bloq.guiding_state_overlap) + print(1 / bloq.guiding_state_overlap_guarantee.overlap_probability**0.5) + + +@pytest.mark.parametrize("n", [40, 50, 60, 70, 80, 90, 100]) +@pytest.mark.parametrize("k", [4, 8]) +@pytest.mark.parametrize("c", [2, 3, 4]) +def test_more_costs(n, k, c): + bloq = example_random_instance(k=k, c=c, n=n, m=n, seed=142) + cost = get_cost_value(bloq, QECGatesCost()) + print(cost) + + +@pytest.mark.parametrize("n", [10**4, 10**5]) +def test_large(n): + k = 4 + c = 32 // 4 + bloq = example_random_instance(k=k, c=c, n=n, m=n * 10, seed=142) + cost = get_cost_value(bloq, QECGatesCost()) + print(cost) diff --git a/qualtran/bloqs/max_k_xor_sat/resource/__init__.py b/qualtran/bloqs/max_k_xor_sat/resource/__init__.py new file mode 100644 index 000000000..b7a8a3022 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/resource/__init__.py @@ -0,0 +1,14 @@ +# 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 .phase_gradient import AcquirePhaseGradient, ReleasePhaseGradient diff --git a/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient.py b/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient.py new file mode 100644 index 000000000..05c9683c4 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient.py @@ -0,0 +1,137 @@ +# 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 Optional, TYPE_CHECKING + +import attrs +import numpy as np + +from qualtran import Bloq, ConnectionT, DecomposeTypeError, QDType, QFxp, Register, Side, Signature +from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq +from qualtran.bloqs.rotations import PhaseGradientState +from qualtran.resource_counting.generalizers import _ignore_wrapper +from qualtran.symbolics import is_symbolic, SymbolicInt + +if TYPE_CHECKING: + import quimb.tensor as qtn + + +@attrs.frozen +class AcquirePhaseGradient(_BookkeepingBloq): + r"""Acquire a phase gradient state, used to perform rotations. + + This is treated as a book-keeping bloq at the moment, because it should + be accounted for as a one-time preparation cost. + If we directly used allocations of `PhaseGradientState` each time in + a large algorithm, their costs would add up, which is incorrect. + + To capture the required resource costs of an algorithm, we can use a separate + cost key that only counts this bloq, and takes the max phase gradient size + and computes the cost to synthesize it. + + Args: + bitsize: number of bits of the phase gradient $b_\text{grad}$. + eps: the precision to synthesize the state. + + Registers: + phase_grad: A phase gradient state of `bitsize`, $|\phi_\text{grad}\rangle$. + """ + + bitsize: SymbolicInt + eps: float = 1e-10 + + @property + def signature(self) -> 'Signature': + return Signature([Register('phase_grad', self.phase_dtype, side=Side.RIGHT)]) + + @property + def phase_dtype(self) -> QDType: + return QFxp(self.bitsize, self.bitsize) + + def decompose_bloq(self): + raise DecomposeTypeError(f"{self} is atomic") + + def my_tensors( + self, incoming: dict[str, 'ConnectionT'], outgoing: dict[str, 'ConnectionT'] + ) -> list['qtn.Tensor']: + import quimb.tensor as qtn + + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"cannot compute tensors for symbolic {self}") + + return [ + qtn.Tensor( + data=np.array([1, np.exp(-1j * np.pi / 2**i)]) / np.sqrt(2), + inds=[(outgoing['phase_grad'], i)], + tags=[f'pg_{i}'], + ) + for i in range(self.bitsize) + ] + + @property + def prepare(self) -> PhaseGradientState: + """bloq to prepare the actual phase gradient state""" + return PhaseGradientState(self.bitsize, eps=self.eps) + + def adjoint(self) -> 'ReleasePhaseGradient': + return ReleasePhaseGradient(self.bitsize, self.eps) + + +@attrs.frozen +class ReleasePhaseGradient(_BookkeepingBloq): + """Release a phase gradient resource state. + + Helper to make diagrams cleaner. See `AcquirePhaseGradient` for details. + """ + + bitsize: SymbolicInt + eps: float = 1e-10 + + @property + def signature(self) -> 'Signature': + return Signature([Register('phase_grad', self.phase_dtype, side=Side.LEFT)]) + + @property + def phase_dtype(self) -> QDType: + return QFxp(self.bitsize, self.bitsize) + + def decompose_bloq(self): + raise DecomposeTypeError(f"{self} is atomic") + + def my_tensors( + self, incoming: dict[str, 'ConnectionT'], outgoing: dict[str, 'ConnectionT'] + ) -> list['qtn.Tensor']: + import quimb.tensor as qtn + + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"cannot compute tensors for symbolic {self}") + + return [ + qtn.Tensor( + data=np.array([1, np.exp(1j * np.pi / 2**i)]) / np.sqrt(2), + inds=[(incoming['phase_grad'], i)], + tags=[f'pg_{i}'], + ) + for i in range(self.bitsize) + ] + + def adjoint(self) -> 'AcquirePhaseGradient': + return AcquirePhaseGradient(self.bitsize, self.eps) + + +def ignore_resource_alloc_free(b: Bloq) -> Optional[Bloq]: + """A generalizer that ignores split and join operations.""" + if isinstance(b, (AcquirePhaseGradient, ReleasePhaseGradient)): + return None + + return _ignore_wrapper(ignore_resource_alloc_free, b) diff --git a/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient_test.py b/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient_test.py new file mode 100644 index 000000000..5cf660cc1 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/resource/phase_gradient_test.py @@ -0,0 +1,31 @@ +# 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 numpy as np +import pytest + +from qualtran.bloqs.max_k_xor_sat.resource.phase_gradient import AcquirePhaseGradient + + +@pytest.mark.parametrize('n', [3, 4, 5, 10, 12]) +@pytest.mark.parametrize('adjoint', [False, True]) +def test_tensor(n: int, adjoint: bool): + phase_grad = AcquirePhaseGradient(n) + + actual = (phase_grad.adjoint() if adjoint else phase_grad).tensor_contract() + + expected = phase_grad.prepare.tensor_contract() + if adjoint: + expected = expected.conj().T + + np.testing.assert_allclose(actual, expected) diff --git a/qualtran/bloqs/max_k_xor_sat/shims.py b/qualtran/bloqs/max_k_xor_sat/shims.py new file mode 100644 index 000000000..8f3b99435 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/shims.py @@ -0,0 +1,148 @@ +# 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 Optional + +import attrs +from attrs import frozen + +from qualtran import ( + Bloq, + BloqBuilder, + CtrlSpec, + DecomposeTypeError, + QAny, + QBit, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import Hadamard, OnEach +from qualtran.bloqs.mcmt import MultiControlX +from qualtran.resource_counting import ( + BloqCountDictT, + CostKey, + GateCounts, + QECGatesCost, + SympySymbolAllocator, +) +from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt + + +@frozen +class ArbitraryGate(Bloq): + """Placeholder gate for costing + + Footnote 18, page 29: + By “gate complexity”, we mean the total number of (arbitrary) 1- and 2-qubit gates + used by the quantum algorithm. These gates can be further represented using a + finite universal gate set with a logarithmic overhead. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Section 4.4.2 for algorithm. Section 2.4 for definitions and notation. + """ + + n_ctrls: SymbolicInt = 0 + + @property + def signature(self) -> 'Signature': + return Signature.build(q=2) + + def my_static_costs(self, cost_key: 'CostKey'): + if isinstance(cost_key, QECGatesCost): + # placeholder cost: reduce controls to single bit, and use C-SU2 (say). + return GateCounts(rotation=1, and_bloq=self.n_ctrls) + + return NotImplemented + + def adjoint(self) -> 'Bloq': + return self + + def get_ctrl_system(self, ctrl_spec: CtrlSpec): + ctrl_bloq = attrs.evolve(self, n_ctrls=self.n_ctrls + ctrl_spec.num_qubits) + + return ctrl_bloq, NotImplemented + + +def generalize_1_2_qubit_gates(b: Bloq) -> Optional[Bloq]: + from qualtran.bloqs.basic_gates import GlobalPhase, Identity + from qualtran.bloqs.bookkeeping import ArbitraryClifford + from qualtran.resource_counting.classify_bloqs import ( + bloq_is_clifford, + bloq_is_rotation, + bloq_is_t_like, + ) + + if bloq_is_t_like(b) or bloq_is_clifford(b) or bloq_is_rotation(b): + return ArbitraryGate() + + if isinstance(b, ArbitraryClifford): + return ArbitraryGate() + + if isinstance(b, (GlobalPhase, Identity)): + return None + + return b + + +@frozen +class ProbabilisticUncompute(Bloq): + """Probabilistically uncompute a register using hadamards, and mark success in a flag qubit + + Apply hadamards to the register, and mark the flag conditioned on all input qubits being 0. + + References: + [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1) + Eq. 129 and Eq. 130. + """ + + bitsize: SymbolicInt + + @property + def signature(self) -> 'Signature': + return Signature([Register('q', QAny(self.bitsize)), Register('flag', QBit())]) + + def build_composite_bloq( + self, bb: 'BloqBuilder', q: 'Soquet', flag: 'Soquet' + ) -> dict[str, 'SoquetT']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"cannot decompose symbolic {self}") + + q = bb.add(OnEach(self.bitsize, Hadamard()), q=q) + + qs = bb.split(q) + qs, flag = bb.add(MultiControlX(cvs=[1] * self.bitsize), controls=qs, target=flag) + q = bb.join(qs) + + return {'q': q, 'flag': flag} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> BloqCountDictT: + return {OnEach(self.bitsize, Hadamard()): 1, MultiControlX(cvs=HasLength(self.bitsize)): 1} + + +@frozen +class ReflectAboutZero(Bloq): + registers: tuple[Register, ...] + global_phase: float = 1 + + @property + def signature(self) -> 'Signature': + return Signature(self.registers) + + def my_static_costs(self, cost_key: 'CostKey'): + if cost_key == QECGatesCost(): + return GateCounts(and_bloq=self.signature.n_qubits()) + + return NotImplemented diff --git a/qualtran/bloqs/max_k_xor_sat/tutorial_guiding_state.ipynb b/qualtran/bloqs/max_k_xor_sat/tutorial_guiding_state.ipynb new file mode 100644 index 000000000..5e3217022 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/tutorial_guiding_state.ipynb @@ -0,0 +1,2133 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 13, + "id": "82896538a83e86e0", + "metadata": { + "ExecuteTime": { + "start_time": "2024-08-29T16:19:45.546609Z" + }, + "jupyter": { + "is_executing": true + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import sympy\n", + "from qualtran.drawing import show_bloq, show_call_graph" + ] + }, + { + "cell_type": "markdown", + "id": "971792d050e3ef26", + "metadata": {}, + "source": [ + "Let us start with a kXOR instance with $n$ variables and $m$ constraints." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "initial_id", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.327449Z", + "start_time": "2024-08-26T16:39:09.321990Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "KXorInstance(n=n, k=k, constraints=HasLength(n=m), max_rhs=2)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.kxor_instance import KXorInstance\n", + "\n", + "n, m, k = sympy.symbols(\"n m k\", positive=True, integer=True)\n", + "inst = KXorInstance.symbolic(n, m, k)\n", + "inst" + ] + }, + { + "cell_type": "markdown", + "id": "cd204c687a2025b1", + "metadata": {}, + "source": [ + "We first prepare the guiding state to use in the guided sparse hamiltonian algorithm.\n", + "The guiding state is defined by the instance, and a parameter $\\ell$ (a multiple of $k$)\n", + "\n", + "From Theorem 4.15 of the paper, this should be a circuit of $O(\\ell m \\log n)$ gates,\n", + "and prepare the state $\\beta |\\Psi\\rangle|0^{\\ell \\log \\ell}\\rangle + |\\perp\\rangle|1\\rangle$,\n", + "where $\\beta \\ge 0.99 / \\ell^{\\ell/2}$." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "49b841dd4be61404", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.374257Z", + "start_time": "2024-08-26T16:39:09.330508Z" + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "GuidingState\n", + "inst=KXorInst ..., ell=c*k\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "HasDuplicates\n", + "l=c*k, dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "SimpleGuidingState\n", + "inst=KXorInst ..., phasegrad_bitsize=30\n", + "\n", + "\n", + "\n", + "b0->b2\n", + "\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "XGate\n", + "\n", + "\n", + "\n", + "b0->b3\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "SortInPlace\n", + "l=c*k, dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b0->b4\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "CCX\n", + "cvs=(0, 0), target_gate=cirq.X\n", + "\n", + "\n", + "\n", + "b0->b5\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "ProbabilisticUncompute\n", + "bitsize=c*k*ceil ...\n", + "\n", + "\n", + "\n", + "b0->b6\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.guiding_state import GuidingState\n", + "\n", + "c = sympy.symbols(\"c\", positive=True, integer=True)\n", + "l = c * k\n", + "guiding_state = GuidingState(inst, l)\n", + "show_call_graph(guiding_state.call_graph(max_depth=1)[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4977ed3aaa506a55", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.426085Z", + "start_time": "2024-08-26T16:39:09.376658Z" + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "T\n", + "T\n", + "\n", + "\n", + "\n", + "Partition_G9\n", + "\n", + "Partition\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "S[0]\n", + "\n", + "\n", + "S[1]\n", + "\n", + "\n", + "S[2]\n", + "\n", + "\n", + "\n", + "T:e->Partition_G9:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "ancilla_G45\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Partition_G14\n", + "\n", + "Partition\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "ancilla\n", + "\n", + "\n", + "flags[0]\n", + "\n", + "\n", + "flags[1]\n", + "\n", + "\n", + "flags[2]\n", + "\n", + "\n", + "\n", + "ancilla_G45:e->Partition_G14:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(3⋅k)⌉ ...\n", + "\n", + "\n", + "\n", + "Partition\n", + "\n", + "Partition\n", + "\n", + "\n", + "x\n", + "\n", + "ancilla\n", + "\n", + "\n", + "flags[0]\n", + "\n", + "\n", + "flags[1]\n", + "\n", + "\n", + "flags[2]\n", + "\n", + "\n", + "\n", + "\n", + "ancilla_G30\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Partition:e->ancilla_G30:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(3⋅k)⌉ ...\n", + "\n", + "\n", + "\n", + "SortInPlace\n", + "\n", + "SortInPlace\n", + "\n", + "input\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute\n", + "\n", + "ProbabilisticUncompute\n", + "\n", + "q\n", + "\n", + "flag\n", + "\n", + "\n", + "\n", + "SortInPlace:e->ProbabilisticUncompute:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(3⋅k)⌉\n", + "\n", + "\n", + "\n", + "HasDuplicates\n", + "\n", + "HasDuplicates\n", + "\n", + "input\n", + "\n", + "flag\n", + "\n", + "\n", + "\n", + "SortInPlace:e->HasDuplicates:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute:e->Partition:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(3⋅k)⌉\n", + "\n", + "\n", + "\n", + "MultiControlX\n", + "\n", + "MultiControlX\n", + "\n", + "controls[0]\n", + "\n", + "controls[1]\n", + "\n", + "target\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "Partition_G1\n", + "\n", + "Partition\n", + "\n", + "\n", + "x\n", + "\n", + "S[0]\n", + "\n", + "\n", + "S[1]\n", + "\n", + "\n", + "S[2]\n", + "\n", + "\n", + "\n", + "\n", + "Partition_G1:e->SortInPlace:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "SimpleGuidingState\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "SimpleGuidingState:e->Partition_G1:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "XGate\n", + "\n", + "XGate\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "XGate:e->Partition:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "HasDuplicates:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "T_G44\n", + "T\n", + "\n", + "\n", + "\n", + "HasDuplicates:e->T_G44:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G7\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G7:e->Partition_G1:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "Partition_G9:e->SimpleGuidingState:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "Partition_G9:e->SimpleGuidingState_G7:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G12\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "Partition_G9:e->SimpleGuidingState_G12:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G12:e->Partition_G1:w\n", + "\n", + "\n", + "k⋅⌈log₂(n)⌉\n", + "\n", + "\n", + "\n", + "Partition_G14:e->SortInPlace:w\n", + "\n", + "\n", + "3⋅k⋅⌈log₂(3⋅k)⌉\n", + "\n", + "\n", + "\n", + "Partition_G14:e->ProbabilisticUncompute:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "Partition_G14:e->HasDuplicates:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "Partition_G14:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX:e->Partition:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX:e->Partition:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX:e->XGate:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "guiding_state_3 = GuidingState(inst, 3 * k)\n", + "show_bloq(guiding_state_3.decompose_bloq())" + ] + }, + { + "cell_type": "markdown", + "id": "96b8c98966f509f2", + "metadata": {}, + "source": [ + "We can also build the guiding state for a concrete (non symbolic) instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8c2235778858f43a", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.462970Z", + "start_time": "2024-08-26T16:39:09.428032Z" + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "T_G0\n", + "T\n", + "\n", + "\n", + "\n", + "GuidingState\n", + "\n", + "GuidingState\n", + "\n", + "T\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "T_G0:e->GuidingState:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "ancilla_G3\n", + "ancilla\n", + "\n", + "\n", + "\n", + "ancilla_G3:e->GuidingState:w\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "\n", + "T_G1\n", + "T\n", + "\n", + "\n", + "\n", + "GuidingState:e->T_G1:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "ancilla_G5\n", + "ancilla\n", + "\n", + "\n", + "\n", + "GuidingState:e->ancilla_G5:w\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "inst = KXorInstance.random_instance(n=20, m=100, k=4, planted_advantage=0.8, rng=np.random.default_rng(100))\n", + "guiding_state_concrete = GuidingState(inst, ell=12)\n", + "show_bloq(guiding_state_concrete)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "7bde9ab8a749a772", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.523825Z", + "start_time": "2024-08-26T16:39:09.465224Z" + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "T\n", + "T\n", + "\n", + "\n", + "\n", + "Partition_G15\n", + "\n", + "Partition\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "S[0]\n", + "\n", + "\n", + "S[1]\n", + "\n", + "\n", + "S[2]\n", + "\n", + "\n", + "\n", + "T:e->Partition_G15:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "ancilla_G34\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Partition_G3\n", + "\n", + "Partition\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "ancilla\n", + "\n", + "\n", + "flags[0]\n", + "\n", + "\n", + "flags[1]\n", + "\n", + "\n", + "flags[2]\n", + "\n", + "\n", + "\n", + "ancilla_G34:e->Partition_G3:w\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "\n", + "SimpleGuidingState\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "Partition\n", + "\n", + "Partition\n", + "\n", + "\n", + "x\n", + "\n", + "S[0]\n", + "\n", + "\n", + "S[1]\n", + "\n", + "\n", + "S[2]\n", + "\n", + "\n", + "\n", + "\n", + "SimpleGuidingState:e->Partition:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "SortInPlace\n", + "\n", + "SortInPlace\n", + "\n", + "input\n", + "\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Partition:e->SortInPlace:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G1\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G1:e->Partition:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "XGate\n", + "\n", + "XGate\n", + "\n", + "q\n", + "\n", + "\n", + "\n", + "Partition_G11\n", + "\n", + "Partition\n", + "\n", + "\n", + "x\n", + "\n", + "ancilla\n", + "\n", + "\n", + "flags[0]\n", + "\n", + "\n", + "flags[1]\n", + "\n", + "\n", + "flags[2]\n", + "\n", + "\n", + "\n", + "\n", + "XGate:e->Partition_G11:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute\n", + "\n", + "ProbabilisticUncompute\n", + "\n", + "q\n", + "\n", + "flag\n", + "\n", + "\n", + "\n", + "SortInPlace:e->ProbabilisticUncompute:w\n", + "\n", + "\n", + "48\n", + "\n", + "\n", + "\n", + "HasDuplicates\n", + "\n", + "HasDuplicates\n", + "\n", + "input\n", + "\n", + "flag\n", + "\n", + "\n", + "\n", + "SortInPlace:e->HasDuplicates:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "Partition_G3:e->SortInPlace:w\n", + "\n", + "\n", + "48\n", + "\n", + "\n", + "\n", + "Partition_G3:e->ProbabilisticUncompute:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "Partition_G3:e->HasDuplicates:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX\n", + "\n", + "MultiControlX\n", + "\n", + "controls[0]\n", + "\n", + "controls[1]\n", + "\n", + "target\n", + "\n", + "\n", + "\n", + "Partition_G3:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G6\n", + "\n", + "SimpleGuidingState\n", + "\n", + "S\n", + "\n", + "\n", + "\n", + "SimpleGuidingState_G6:e->Partition:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute:e->Partition_G11:w\n", + "\n", + "\n", + "48\n", + "\n", + "\n", + "\n", + "ProbabilisticUncompute:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "HasDuplicates:e->MultiControlX:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "T_G42\n", + "T\n", + "\n", + "\n", + "\n", + "HasDuplicates:e->T_G42:w\n", + "\n", + "\n", + "60\n", + "\n", + "\n", + "\n", + "ancilla_G22\n", + "ancilla\n", + "\n", + "\n", + "\n", + "Partition_G11:e->ancilla_G22:w\n", + "\n", + "\n", + "51\n", + "\n", + "\n", + "\n", + "Partition_G15:e->SimpleGuidingState:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "Partition_G15:e->SimpleGuidingState_G1:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "Partition_G15:e->SimpleGuidingState_G6:w\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "MultiControlX:e->XGate:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX:e->Partition_G11:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "MultiControlX:e->Partition_G11:w\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_bloq(guiding_state_concrete.decompose_bloq())" + ] + }, + { + "cell_type": "markdown", + "id": "140a7fb3e4417c87", + "metadata": {}, + "source": [ + "Let us evaluate the gate cost for the above bloqs." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8cf9829e6d7bce3f", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.839012Z", + "start_time": "2024-08-26T16:39:09.525868Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "GateCounts(t=0, toffoli=672, cswap=2880, and_bloq=22726, clifford=140518, rotation=0, measurement=22726)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qualtran.resource_counting import get_cost_value, QECGatesCost\n", + "\n", + "get_cost_value(guiding_state_concrete, QECGatesCost())" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "518a98ecdc4cd03", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:09.874380Z", + "start_time": "2024-08-26T16:39:09.840147Z" + } + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 16 c^{2} k^{2} \\cdot \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(n \\right)}}\\right\\rceil + 1\\right) + 16 c^{2} k^{2} \\left\\lceil{\\operatorname{log}_{2}{\\left(n \\right)}}\\right\\rceil + 4 c k \\left\\lceil{\\operatorname{log}_{2}{\\left(c k \\right)}}\\right\\rceil + 4 c k + 4 c \\left(4 m + \\left(2 m + 1\\right) \\left(\\left\\lceil{\\operatorname{log}_{2}{\\left(\\left\\lfloor{2^{k \\left\\lceil{\\operatorname{log}_{2}{\\left(n \\right)}}\\right\\rceil}}\\right\\rfloor \\right)}}\\right\\rceil - 1\\right) - 4\\right) + 224 c + 4 \\cdot \\left(2 c k - 2\\right) \\left(2 \\left\\lceil{\\operatorname{log}_{2}{\\left(n \\right)}}\\right\\rceil + 1\\right) - 8$" + ], + "text/plain": [ + "16*c**2*k**2*(2*ceiling(log2(n)) + 1) + 16*c**2*k**2*ceiling(log2(n)) + 4*c*k*ceiling(log2(c*k)) + 4*c*k + 4*c*(4*m + (2*m + 1)*(ceiling(log2(floor(2**(k*ceiling(log2(n)))))) - 1) - 4) + 224*c + 4*(2*c*k - 2)*(2*ceiling(log2(n)) + 1) - 8" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gc = get_cost_value(guiding_state, QECGatesCost())\n", + "t_cost = gc.total_t_count(ts_per_toffoli=4, ts_per_cswap=4, ts_per_and_bloq=4)\n", + "t_cost" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "66070e9c3dfd10c", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-26T16:39:11.247058Z", + "start_time": "2024-08-26T16:39:09.876298Z" + } + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle O\\left(\\ell^{2} \\operatorname{log}_{2}{\\left(n \\right)} + \\ell m \\operatorname{log}_{2}{\\left(n \\right)} + c m; \\left( \\ell, \\ c, \\ m, \\ n\\right)\\rightarrow \\left( \\infty, \\ \\infty, \\ \\infty, \\ \\infty\\right)\\right)$" + ], + "text/plain": [ + "O(\\ell**2*log2(n) + \\ell*m*log2(n) + c*m, (\\ell, oo), (c, oo), (m, oo), (n, oo))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qualtran.symbolics import ceil, log2, floor\n", + "from qualtran.resource_counting import big_O\n", + "\n", + "# simplify some expressions that sympy could not\n", + "klogn = k * ceil(log2(n))\n", + "klogn_long = ceil(log2(floor(2**klogn)))\n", + "t_cost = t_cost.subs(klogn_long, klogn)\n", + "t_cost = t_cost.simplify()\n", + "\n", + "# replace l with a symbol\n", + "l_symb = sympy.symbols(r\"\\ell\", positive=True, integer=True)\n", + "t_cost = t_cost.subs(c * k, l_symb)\n", + "\n", + "big_O(t_cost) # matches paper Theorem 4.15 (as c, l are O(m))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e47caf91-a16e-441a-a02e-6925ee3ca55a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "GuidingState\n", + "\n", + "Qubits\n", + "\n", + "179\n", + "\n", + "Toffolis\n", + "\n", + "672\n", + "\n", + "CSwaps\n", + "\n", + "2880\n", + "\n", + "Ands\n", + "\n", + "22726\n", + "\n", + "Cliffords\n", + "\n", + "140518\n", + "\n", + "Measurements\n", + "\n", + "22726\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "SortInPlace\n", + "\n", + "Qubits\n", + "\n", + "108\n", + "\n", + "CSwaps\n", + "\n", + "2880\n", + "\n", + "Ands\n", + "\n", + "5184\n", + "\n", + "Cliffords\n", + "\n", + "58752\n", + "\n", + "Measurements\n", + "\n", + "5184\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "SimpleGuidingState\n", + "\n", + "Qubits\n", + "\n", + "88\n", + "\n", + "Toffolis\n", + "\n", + "224\n", + "\n", + "Ands\n", + "\n", + "5762\n", + "\n", + "Cliffords\n", + "\n", + "26478\n", + "\n", + "Measurements\n", + "\n", + "5762\n", + "\n", + "\n", + "\n", + "b0->b2\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "HasDuplicates\n", + "\n", + "Qubits\n", + "\n", + "61\n", + "\n", + "Ands\n", + "\n", + "208\n", + "\n", + "Cliffords\n", + "\n", + "2233\n", + "\n", + "Measurements\n", + "\n", + "208\n", + "\n", + "\n", + "\n", + "b0->b3\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "MultiControlX\n", + "\n", + "Qubits\n", + "\n", + "4\n", + "\n", + "Ands\n", + "\n", + "1\n", + "\n", + "Cliffords\n", + "\n", + "2\n", + "\n", + "Measurements\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b0->b4\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "ProbabilisticUncompute\n", + "\n", + "Qubits\n", + "\n", + "96\n", + "\n", + "Ands\n", + "\n", + "47\n", + "\n", + "Cliffords\n", + "\n", + "96\n", + "\n", + "Measurements\n", + "\n", + "47\n", + "\n", + "\n", + "\n", + "b0->b5\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b23\n", + "\n", + "XGate\n", + "\n", + "Qubits\n", + "\n", + "1\n", + "\n", + "Cliffords\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b0->b23\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "Comparator\n", + "\n", + "Qubits\n", + "\n", + "25\n", + "\n", + "CSwaps\n", + "\n", + "5\n", + "\n", + "Ands\n", + "\n", + "9\n", + "\n", + "Cliffords\n", + "\n", + "102\n", + "\n", + "Measurements\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b1->b6\n", + "\n", + "\n", + "288\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "Adjoint(subbloq=Comparator)\n", + "\n", + "Qubits\n", + "\n", + "25\n", + "\n", + "CSwaps\n", + "\n", + "5\n", + "\n", + "Ands\n", + "\n", + "9\n", + "\n", + "Cliffords\n", + "\n", + "102\n", + "\n", + "Measurements\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b1->b7\n", + "\n", + "\n", + "288\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "AcquirePhaseGradient\n", + "\n", + "Qubits\n", + "\n", + "30\n", + "\n", + "\n", + "\n", + "b2->b8\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b9\n", + "\n", + "SparseStatePreparationViaRotations\n", + "\n", + "Qubits\n", + "\n", + "88\n", + "\n", + "Toffolis\n", + "\n", + "224\n", + "\n", + "Ands\n", + "\n", + "5762\n", + "\n", + "Cliffords\n", + "\n", + "26478\n", + "\n", + "Measurements\n", + "\n", + "5762\n", + "\n", + "\n", + "\n", + "b2->b9\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b10\n", + "\n", + "ReleasePhaseGradient\n", + "\n", + "Qubits\n", + "\n", + "30\n", + "\n", + "\n", + "\n", + "b2->b10\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b11\n", + "\n", + "LessThanEqual\n", + "\n", + "Qubits\n", + "\n", + "25\n", + "\n", + "Ands\n", + "\n", + "9\n", + "\n", + "Cliffords\n", + "\n", + "101\n", + "\n", + "Measurements\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b3->b11\n", + "\n", + "\n", + "22\n", + "\n", + "\n", + "\n", + "b12\n", + "\n", + "MultiControlX\n", + "\n", + "Qubits\n", + "\n", + "22\n", + "\n", + "Ands\n", + "\n", + "10\n", + "\n", + "Cliffords\n", + "\n", + "11\n", + "\n", + "Measurements\n", + "\n", + "10\n", + "\n", + "\n", + "\n", + "b3->b12\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13\n", + "\n", + "C[2][XGate]\n", + "\n", + "Qubits\n", + "\n", + "4\n", + "\n", + "Ands\n", + "\n", + "1\n", + "\n", + "Cliffords\n", + "\n", + "2\n", + "\n", + "Measurements\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b4->b13\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b14\n", + "\n", + "MultiControlX\n", + "\n", + "Qubits\n", + "\n", + "96\n", + "\n", + "Ands\n", + "\n", + "47\n", + "\n", + "Cliffords\n", + "\n", + "48\n", + "\n", + "Measurements\n", + "\n", + "47\n", + "\n", + "\n", + "\n", + "b5->b14\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b15\n", + "\n", + "H⨂48\n", + "\n", + "Qubits\n", + "\n", + "48\n", + "\n", + "Cliffords\n", + "\n", + "48\n", + "\n", + "\n", + "\n", + "b5->b15\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b16\n", + "\n", + "GreaterThan\n", + "\n", + "Qubits\n", + "\n", + "25\n", + "\n", + "Ands\n", + "\n", + "9\n", + "\n", + "Cliffords\n", + "\n", + "102\n", + "\n", + "Measurements\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b6->b16\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b17\n", + "\n", + "Allocate\n", + "\n", + "Qubits\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b6->b17\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b20\n", + "\n", + "CSwap\n", + "\n", + "Qubits\n", + "\n", + "11\n", + "\n", + "CSwaps\n", + "\n", + "5\n", + "\n", + "\n", + "\n", + "b6->b20\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b18\n", + "\n", + "Adjoint(subbloq=GreaterThan)\n", + "\n", + "Qubits\n", + "\n", + "25\n", + "\n", + "Ands\n", + "\n", + "9\n", + "\n", + "Cliffords\n", + "\n", + "102\n", + "\n", + "Measurements\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "b7->b18\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b19\n", + "\n", + "Free\n", + "\n", + "Qubits\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7->b19\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7->b20\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b21\n", + "\n", + "Permutation\n", + "\n", + "Qubits\n", + "\n", + "40\n", + "\n", + "Ands\n", + "\n", + "5700\n", + "\n", + "Cliffords\n", + "\n", + "25868\n", + "\n", + "Measurements\n", + "\n", + "5700\n", + "\n", + "\n", + "\n", + "b9->b21\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b22\n", + "\n", + "StatePreparationViaRotations\n", + "\n", + "Qubits\n", + "\n", + "75\n", + "\n", + "Toffolis\n", + "\n", + "224\n", + "\n", + "Ands\n", + "\n", + "62\n", + "\n", + "Cliffords\n", + "\n", + "610\n", + "\n", + "Measurements\n", + "\n", + "62\n", + "\n", + "\n", + "\n", + "b9->b22\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b11->b23\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24\n", + "\n", + "BiQubitsMixer\n", + "\n", + "Qubits\n", + "\n", + "7\n", + "\n", + "Ands\n", + "\n", + "2\n", + "\n", + "Cliffords\n", + "\n", + "10\n", + "\n", + "\n", + "\n", + "b11->b24\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b25\n", + "\n", + "BiQubitsMixer\n", + "\n", + "Qubits\n", + "\n", + "7\n", + "\n", + "Cliffords\n", + "\n", + "12\n", + "\n", + "Measurements\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b11->b25\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "b26\n", + "\n", + "SingleQubitCompare\n", + "\n", + "Qubits\n", + "\n", + "4\n", + "\n", + "Ands\n", + "\n", + "1\n", + "\n", + "Cliffords\n", + "\n", + "5\n", + "\n", + "\n", + "\n", + "b11->b26\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b27\n", + "\n", + "SingleQubitCompare\n", + "\n", + "Qubits\n", + "\n", + "4\n", + "\n", + "Cliffords\n", + "\n", + "6\n", + "\n", + "Measurements\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b11->b27\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b30\n", + "\n", + "CNOT\n", + "\n", + "Qubits\n", + "\n", + "2\n", + "\n", + "Cliffords\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b11->b30\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b28\n", + "\n", + "C[11][XGate]\n", + "\n", + "Qubits\n", + "\n", + "22\n", + "\n", + "Ands\n", + "\n", + "10\n", + "\n", + "Cliffords\n", + "\n", + "11\n", + "\n", + "Measurements\n", + "\n", + "10\n", + "\n", + "\n", + "\n", + "b12->b28\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b29\n", + "\n", + "Adjoint(subbloq=CtrlSpecAnd)\n", + "\n", + "Qubits\n", + "\n", + "3\n", + "\n", + "Cliffords\n", + "\n", + "1\n", + "\n", + "Measurements\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13->b29\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13->b30\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b31\n", + "\n", + "CtrlSpecAnd\n", + "\n", + "Qubits\n", + "\n", + "3\n", + "\n", + "Ands\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13->b31\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b32\n", + "\n", + "C[48][XGate]\n", + "\n", + "Qubits\n", + "\n", + "96\n", + "\n", + "Ands\n", + "\n", + "47\n", + "\n", + "Cliffords\n", + "\n", + "48\n", + "\n", + "Measurements\n", + "\n", + "47\n", + "\n", + "\n", + "\n", + "b14->b32\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b33\n", + "\n", + "H\n", + "\n", + "Qubits\n", + "\n", + "1\n", + "\n", + "Cliffords\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b15->b33\n", + "\n", + "\n", + "48\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_call_graph(guiding_state_concrete, max_depth=3)" + ] + }, + { + "cell_type": "markdown", + "id": "c459ef23c4ac7335", + "metadata": {}, + "source": [ + "As we know that $c = \\ell/k \\le \\ell$ and $\\ell \\le m$, the above expression matches the paper result of $O(\\ell m \\log_2(n))$ 1/2-qubit gates.\n", + "" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/max_k_xor_sat/tutorial_planted_noisy_kxor.py.ipynb b/qualtran/bloqs/max_k_xor_sat/tutorial_planted_noisy_kxor.py.ipynb new file mode 100644 index 000000000..e72a1ed87 --- /dev/null +++ b/qualtran/bloqs/max_k_xor_sat/tutorial_planted_noisy_kxor.py.ipynb @@ -0,0 +1,2526 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f9ab6a67-1a3c-459f-9716-cda924fa4efe", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "eceae6a5-5497-4ea9-8c24-0abd0f45554c", + "metadata": {}, + "source": [ + "### Implementing [Quartic quantum speedups for planted inference](https://arxiv.org/abs/2406.19378v1)" + ] + }, + { + "cell_type": "markdown", + "id": "d5eb2c1f-a57d-4b18-8b26-454da506578b", + "metadata": {}, + "source": [ + "### Problem Definition" + ] + }, + { + "cell_type": "markdown", + "id": "f6e0f089-9c57-4d87-8478-5b6660cb6395", + "metadata": {}, + "source": [ + "**Definition 2.2 (simplified):**\n", + "A kXOR instance $\\mathcal{I}$ on $n$ variables in ${0, 1}$ is a collection of $m$ constraints, each of the form\n", + "$$ x_{c_1} \\oplus x_{c_2} \\oplus ... x_{c_k} = b $$ " + ] + }, + { + "cell_type": "markdown", + "id": "ebd9642b-99a2-4ee4-b905-826174d5a8c7", + "metadata": {}, + "source": [ + "**Notation 2.3**\n", + "Random Instance: Pick each clause independently:\n", + "- Pick $C$, a $k$ subset of $[n] = \\{1, ... n\\}$ uniformly at random.\n", + "- Pick $b \\in {0, 1}$ uniformly at random." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1756cf95-b8ad-42ca-bb35-59528bf96538", + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat import KXorInstance, Constraint\n", + "\n", + "n, k = 10, 4\n", + "cs = (\n", + " Constraint((0, 1, 2, 3), -1), # read: x_0 ^ x_1 ^ x_2 ^ x_3 = 0\n", + " Constraint((0, 2, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((0, 3, 4, 5), 1),\n", + " Constraint((1, 2, 3, 4), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((1, 3, 4, 5), -1),\n", + " Constraint((2, 3, 4, 5), 1),\n", + ")\n", + "simple_inst = KXorInstance(n, k, cs)\n", + "simple_inst" + ] + }, + { + "cell_type": "markdown", + "id": "5fc71a1f-e1d4-44fb-b657-8d43af87626f", + "metadata": {}, + "source": [ + "**Notation 2.4 (simplified)** Planted Instance:\n", + "Given $\\rho \\in [0, 1]$ (the _planted advantage_),\n", + "\n", + "first pick a secret assignment $z \\in \\{0, 1\\}^n$.\n", + "Now pick each clause independently by: \n", + "- Pick $C$, a $k$ subset of $[n]$ uniformly at random.\n", + "- Pick noise $\\eta \\in {0, 1}$, s.t. $\\eta = 0$ with probability $(1 + \\rho)/2$\n", + "- Set $b = C(z) \\oplus \\eta$\n", + "\n", + "Note: when $\\rho = 0$, the noise is random, and when $\\rho = 1$, there is no noise." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d334a8e-3792-4ece-9f9c-d77da3111aa2", + "metadata": {}, + "outputs": [], + "source": [ + "random_inst = KXorInstance.random_instance(\n", + " n=10, \n", + " m=20, \n", + " k=4,\n", + " planted_advantage=0.8,\n", + " rng=np.random.default_rng(42),\n", + ")\n", + "random_inst" + ] + }, + { + "cell_type": "markdown", + "id": "cd35802e-1778-44de-81bd-9356da3999a7", + "metadata": {}, + "source": [ + "## Problem\n", + "\n", + "**Problem 2.6 (Planted Noisy kXOR)**\n", + "Given $\\rho \\in (0, 1)$, and an instance $\\mathcal{I}$ that is promised to be either drawn from the random distribution or planted distribution (with $\\rho$), distinguish which case it is." + ] + }, + { + "cell_type": "markdown", + "id": "4e401d1e-157c-4f49-adfe-9ceeb315ba6d", + "metadata": {}, + "source": [ + "## Kikuchi Method\n", + "This is a technique to reduce $k$XOR problems to $2$XOR problems, on an exponentially larger set of variables (say, $O(n^k)$).\n", + "The 2XOR is known to be efficiently solvable by some spectral analysis.\n", + "\n", + "For this, we pick our new variables as subsets of $[n]$ of size $k$, call them $X_S$ for each subset $S$.\n", + "There are ${n \\choose k}$ variables now, and for $k \\ll n$, this is about $O(n^k)$.\n", + "\n", + "The equations are of the form $X_S \\oplus X_T = b(S, T)$ for every $S, T$ with $|S \\Delta T| = k$.\n", + "Here $b(S, T)$ is the xor of all variables in S and T (common ones cancel out, leaving only the $k$ as above)" + ] + }, + { + "cell_type": "markdown", + "id": "da3f5e5c-67dd-4717-962b-23052fdd30ad", + "metadata": {}, + "source": [ + "## Quantum Algorithm\n", + "\n", + "**Theorem 4.18 (simplified)**\n", + "Let $k$ (even) and $\\rho \\in (0, 1)$ be known constants.\n", + "\n", + "We are given an instance $\\mathcal{I}$ which is either random or planted (with advantage $\\rho$),\n", + "where the number of constraints $m$ is picked above a given threshold (see Alice Theorem).\n", + "\n", + "For a parameter $\\ell$, if we have a classical _Kikuchi style_ algorithm with complexity $\\tilde{O}(n^\\ell)$,\n", + "then there is a quantum algorithm with $\\tilde{O}(n^{\\ell/4} m \\ell^{O{\\ell}} \\log^{\\ell/2k}n)$." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "9b5a6bbb-e895-47d9-8ea6-5ab1f07329e3", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-27T13:03:40.693090Z", + "start_time": "2024-08-27T13:03:38.179464Z" + } + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.max_k_xor_sat.planted_noisy_kxor import PlantedNoisyKXOR\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b5e3a55c-00a9-40a4-b309-79fa008b0760", + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-27T13:03:40.693090Z", + "start_time": "2024-08-27T13:03:38.179464Z" + } + }, + "outputs": [], + "source": [ + "def make_algo_example():\n", + " k = 4\n", + " n, m = 100, 1000\n", + " rho = 0.8\n", + " \n", + " c = 2 # Kikuchi param: ell = c * k\n", + " \n", + " # generate instance\n", + " rng = np.random.default_rng(142)\n", + " ell = c * k\n", + " inst = KXorInstance.random_instance(n=n, m=m, k=k, planted_advantage=rho, rng=rng)\n", + " algo_bloq = PlantedNoisyKXOR.from_inst(inst=inst, ell=ell, rho=rho, zeta=1 / np.log(n), rng=rng)\n", + "\n", + " expected_complexity = n ** (ell/4) * m * ell**ell * np.log(n)**(c//2)\n", + "\n", + " return algo_bloq, expected_complexity" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "46dc55f4-3873-40a2-9509-b723493eeb85", + "metadata": {}, + "outputs": [], + "source": [ + "bloq, cost_O_tilde = make_algo_example()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "04de6797-5986-461e-b040-2ec7fab2bcc2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "my_graph\n", + "\n", + "\n", + "\n", + "phase_estimate_G1\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "PlantedNoisyKXOR\n", + "\n", + "PlantedNoisyKXOR\n", + "\n", + "phase_estimate\n", + "\n", + "system\n", + "\n", + "walk_ancilla\n", + "\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "phase_estimate_G1:e->PlantedNoisyKXOR:w\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "system_G10\n", + "system\n", + "\n", + "\n", + "\n", + "system_G10:e->PlantedNoisyKXOR:w\n", + "\n", + "\n", + "56\n", + "\n", + "\n", + "\n", + "walk_ancilla_G0\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "walk_ancilla_G0:e->PlantedNoisyKXOR:w\n", + "\n", + "\n", + "58\n", + "\n", + "\n", + "\n", + "guide_ancilla_G7\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "guide_ancilla_G7:e->PlantedNoisyKXOR:w\n", + "\n", + "\n", + "27\n", + "\n", + "\n", + "\n", + "phase_estimate_G9\n", + "phase_estimate\n", + "\n", + "\n", + "\n", + "PlantedNoisyKXOR:e->phase_estimate_G9:w\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "system_G4\n", + "system\n", + "\n", + "\n", + "\n", + "PlantedNoisyKXOR:e->system_G4:w\n", + "\n", + "\n", + "56\n", + "\n", + "\n", + "\n", + "walk_ancilla_G8\n", + "walk_ancilla\n", + "\n", + "\n", + "\n", + "PlantedNoisyKXOR:e->walk_ancilla_G8:w\n", + "\n", + "\n", + "58\n", + "\n", + "\n", + "\n", + "guide_ancilla_G2\n", + "guide_ancilla\n", + "\n", + "\n", + "\n", + "PlantedNoisyKXOR:e->guide_ancilla_G2:w\n", + "\n", + "\n", + "27\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_bloq(bloq)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "6cdc8096-84da-4358-b068-1e4438be51e4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "counts\n", + "\n", + "\n", + "\n", + "b0\n", + "\n", + "PlantedNoisyKXOR\n", + "inst_guide=KXorInst ..., inst_solve=KXorInst ..., ell=8, rho=0.8\n", + "\n", + "\n", + "\n", + "b1\n", + "\n", + "GuidedHamiltonian\n", + "hamiltonian=KikuchiH ..., guiding_state=BlackBox ..., lambd=19.104, alpha=0.958280 ..., gamma=8.872960 ...\n", + "\n", + "\n", + "\n", + "b0->b1\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b2\n", + "\n", + "ZGate\n", + "\n", + "\n", + "\n", + "b1->b2\n", + "\n", + "\n", + "11270195255\n", + "\n", + "\n", + "\n", + "b3\n", + "\n", + "GuidedHamiltonianPhaseEstimation†\n", + "subbloq=GuidedHa ...\n", + "\n", + "\n", + "\n", + "b1->b3\n", + "\n", + "\n", + "11270195255\n", + "\n", + "\n", + "\n", + "b4\n", + "\n", + "C[148]Z\n", + "cvs=(1, 1, 1 ..., target_gate=cirq.Z\n", + "\n", + "\n", + "\n", + "b1->b4\n", + "\n", + "\n", + "11270195255\n", + "\n", + "\n", + "\n", + "b5\n", + "\n", + "GuidedHamiltonianPhaseEstimation\n", + "hamiltonian=KikuchiH ..., guiding_state=BlackBox ..., precision=0.762791 ..., fail_prob=6.985631 ...\n", + "\n", + "\n", + "\n", + "b1->b5\n", + "\n", + "\n", + "11270195256\n", + "\n", + "\n", + "\n", + "b6\n", + "\n", + "QubitizationQPE[7]†\n", + "subbloq=Qubitiza ...\n", + "\n", + "\n", + "\n", + "b3->b6\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b7\n", + "\n", + "BBPrepare[GuidingState]†\n", + "subbloq=BlackBox ...\n", + "\n", + "\n", + "\n", + "b3->b7\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b8\n", + "\n", + "C[148][ZGate]\n", + "subbloq=ZGate(), ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b4->b8\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b9\n", + "\n", + "QubitizationQPE[7]\n", + "walk=Qubitize ..., ctrl_state_prep=KaiserWi ..., qft_inv=Adjoint( ...\n", + "\n", + "\n", + "\n", + "b5->b9\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b10\n", + "\n", + "BBPrepare[GuidingState]\n", + "prepare=GuidingS ...\n", + "\n", + "\n", + "\n", + "b5->b10\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b11\n", + "\n", + "C[B[H]†]\n", + "subbloq=Adjoint( ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b6->b11\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b12\n", + "\n", + "QFTTextBook\n", + "bitsize=7, with_reverse=True\n", + "\n", + "\n", + "\n", + "b6->b12\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b13\n", + "\n", + "B[H]†\n", + "subbloq=Qubitize ...\n", + "\n", + "\n", + "\n", + "b6->b13\n", + "\n", + "\n", + "126\n", + "\n", + "\n", + "\n", + "b14\n", + "\n", + "KaiserWindowState†\n", + "subbloq=KaiserWi ...\n", + "\n", + "\n", + "\n", + "b6->b14\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b22\n", + "\n", + "ReflectionUsingPrepare\n", + "prepare_gate=BlackBox ..., control_val=0, global_phase=-1, eps=1e-11\n", + "\n", + "\n", + "\n", + "b6->b22\n", + "\n", + "\n", + "12\n", + "\n", + "\n", + "\n", + "b15\n", + "\n", + "GuidingState†\n", + "subbloq=AutoPart ...\n", + "\n", + "\n", + "\n", + "b7->b15\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b16\n", + "\n", + "CZ\n", + "\n", + "\n", + "\n", + "b8->b16\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b17\n", + "\n", + "CtrlSpecAnd\n", + "ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b8->b17\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b18\n", + "\n", + "CtrlSpecAnd†\n", + "subbloq=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b8->b18\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b19\n", + "\n", + "KaiserWindowState\n", + "bitsize=7, alpha=22.10225 ...\n", + "\n", + "\n", + "\n", + "b9->b19\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b20\n", + "\n", + "QFTTextBook†\n", + "subbloq=QFTTextB ...\n", + "\n", + "\n", + "\n", + "b9->b20\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b21\n", + "\n", + "B[H]\n", + "block_encoding=KikuchiH ...\n", + "\n", + "\n", + "\n", + "b9->b21\n", + "\n", + "\n", + "126\n", + "\n", + "\n", + "\n", + "b9->b22\n", + "\n", + "\n", + "12\n", + "\n", + "\n", + "\n", + "b23\n", + "\n", + "C[B[H]]\n", + "subbloq=Qubitize ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b9->b23\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24\n", + "\n", + "GuidingState\n", + "bloq=GuidingS ..., partitions=((Regist ..., left_only=False\n", + "\n", + "\n", + "\n", + "b10->b24\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b25\n", + "\n", + "C[B[H]†]\n", + "subbloq=Adjoint( ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b11->b25\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b49\n", + "\n", + "ReflectionUsingPrepare\n", + "prepare_gate=BlackBox ..., control_val=1, global_phase=-1, eps=1e-11\n", + "\n", + "\n", + "\n", + "b11->b49\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b26\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=1, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b26\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b27\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=5, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b27\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b28\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=6, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b28\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b29\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=4, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b29\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b30\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=2, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b30\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b31\n", + "\n", + "PhaseGradientUnitary\n", + "bitsize=3, exponent=0.5, is_controlled=True, eps=1e-10\n", + "\n", + "\n", + "\n", + "b12->b31\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b38\n", + "\n", + "Hadamard\n", + "\n", + "\n", + "\n", + "b12->b38\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b45\n", + "\n", + "TwoBitSwap\n", + "\n", + "\n", + "\n", + "b12->b45\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b32\n", + "\n", + "B[H]†\n", + "subbloq=KikuchiH ...\n", + "\n", + "\n", + "\n", + "b13->b32\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b47\n", + "\n", + "ReflectionUsingPrepare\n", + "prepare_gate=BlackBox ..., control_val=None, global_phase=-1, eps=1e-11\n", + "\n", + "\n", + "\n", + "b13->b47\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b33\n", + "\n", + "StatePreparationViaRotations†\n", + "subbloq=StatePre ...\n", + "\n", + "\n", + "\n", + "b14->b33\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b34\n", + "\n", + "GuidingState†\n", + "subbloq=GuidingS ...\n", + "\n", + "\n", + "\n", + "b15->b34\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b51\n", + "\n", + "Partition\n", + "n=56, regs=(Registe ..., partition=True\n", + "\n", + "\n", + "\n", + "b15->b51\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b52\n", + "\n", + "Partition\n", + "n=27, regs=(Registe ..., partition=False\n", + "\n", + "\n", + "\n", + "b15->b52\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b53\n", + "\n", + "Partition\n", + "n=27, regs=(Registe ..., partition=True\n", + "\n", + "\n", + "\n", + "b15->b53\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b54\n", + "\n", + "Partition\n", + "n=56, regs=(Registe ..., partition=False\n", + "\n", + "\n", + "\n", + "b15->b54\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b35\n", + "\n", + "MultiAnd\n", + "cvs=(1, 1, 1 ...\n", + "\n", + "\n", + "\n", + "b17->b35\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b36\n", + "\n", + "MultiAnd†\n", + "subbloq=MultiAnd ...\n", + "\n", + "\n", + "\n", + "b18->b36\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b37\n", + "\n", + "StatePreparationViaRotations\n", + "state_coefficients=(3.94567 ..., phase_bitsize=7, control_bitsize=0, uncompute=False\n", + "\n", + "\n", + "\n", + "b19->b37\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b20->b38\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b39\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b39\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b40\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b40\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b41\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b41\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b42\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b42\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b43\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b43\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b44\n", + "\n", + "PhaseGradientUnitary†\n", + "subbloq=PhaseGra ...\n", + "\n", + "\n", + "\n", + "b20->b44\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b20->b45\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "b46\n", + "\n", + "B[H]\n", + "inst=KXorInst ..., ell=8, entry_bitsize=10, s=24\n", + "\n", + "\n", + "\n", + "b21->b46\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b21->b47\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b48\n", + "\n", + "ZPowGate\n", + "exponent=-1.0, global_shift=-1, eps=1e-11\n", + "\n", + "\n", + "\n", + "b22->b48\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b125\n", + "\n", + "BBPrepare[PrepareIdentity]†\n", + "subbloq=BlackBox ...\n", + "\n", + "\n", + "\n", + "b22->b125\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b127\n", + "\n", + "BBPrepare[PrepareIdentity]\n", + "prepare=PrepareI ...\n", + "\n", + "\n", + "\n", + "b22->b127\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b128\n", + "\n", + "C[58]Z\n", + "cvs=(0, 0, 0 ..., target_gate=cirq.Z\n", + "\n", + "\n", + "\n", + "b22->b128\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b23->b49\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b50\n", + "\n", + "C[B[H]]\n", + "subbloq=KikuchiH ..., ctrl_spec=CtrlSpec ...\n", + "\n", + "\n", + "\n", + "b23->b50\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24->b51\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24->b52\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24->b53\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b24->b54\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b55\n", + "\n", + "GuidingState\n", + "inst=KXorInst ..., ell=8\n", + "\n", + "\n", + "\n", + "b24->b55\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b129\n", + "\n", + "B[SparseMatrixHermitian]\n", + "col_oracle=BlackBox ..., entry_oracle=BlackBox ..., eps=0, cv=1\n", + "\n", + "\n", + "\n", + "b25->b129\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b56\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=1e-10\n", + "\n", + "\n", + "\n", + "b26->b56\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b57\n", + "\n", + "CZPowGate\n", + "exponent=0.25, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b27->b57\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b58\n", + "\n", + "CZPowGate\n", + "exponent=0.125, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b27->b58\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b59\n", + "\n", + "CZPowGate\n", + "exponent=0.0625, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b27->b59\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b60\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b27->b60\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b61\n", + "\n", + "CZPowGate\n", + "exponent=0.03125, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b27->b61\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b62\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b62\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b63\n", + "\n", + "CZPowGate\n", + "exponent=0.125, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b63\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b64\n", + "\n", + "CZPowGate\n", + "exponent=0.03125, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b64\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b65\n", + "\n", + "CZPowGate\n", + "exponent=0.015625, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b65\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b66\n", + "\n", + "CZPowGate\n", + "exponent=0.0625, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b66\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b67\n", + "\n", + "CZPowGate\n", + "exponent=0.25, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b28->b67\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b68\n", + "\n", + "CZPowGate\n", + "exponent=0.25, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b29->b68\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b69\n", + "\n", + "CZPowGate\n", + "exponent=0.0625, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b29->b69\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b70\n", + "\n", + "CZPowGate\n", + "exponent=0.125, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b29->b70\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b71\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b29->b71\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b72\n", + "\n", + "CZPowGate\n", + "exponent=0.25, global_shift=0.0, eps=5e-11\n", + "\n", + "\n", + "\n", + "b30->b72\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b73\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=5e-11\n", + "\n", + "\n", + "\n", + "b30->b73\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b74\n", + "\n", + "CZPowGate\n", + "exponent=0.5, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b31->b74\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b75\n", + "\n", + "CZPowGate\n", + "exponent=0.25, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b31->b75\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b76\n", + "\n", + "CZPowGate\n", + "exponent=0.125, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b31->b76\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b123\n", + "\n", + "B[SparseMatrixHermitian]\n", + "col_oracle=BlackBox ..., entry_oracle=BlackBox ..., eps=0, cv=None\n", + "\n", + "\n", + "\n", + "b32->b123\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b77\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b77\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b78\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b78\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b79\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b79\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b80\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b80\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b81\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b81\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b82\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b82\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b83\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b83\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b84\n", + "\n", + "PRGAViaPhaseGradient†\n", + "subbloq=PRGAViaP ...\n", + "\n", + "\n", + "\n", + "b33->b84\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b96\n", + "\n", + "Rx\n", + "angle=-1.57079 ..., eps=1e-11\n", + "\n", + "\n", + "\n", + "b33->b96\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b97\n", + "\n", + "Rx\n", + "angle=1.570796 ..., eps=1e-11\n", + "\n", + "\n", + "\n", + "b33->b97\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b130\n", + "\n", + "XGate\n", + "\n", + "\n", + "\n", + "b33->b130\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b85\n", + "\n", + "CCX†\n", + "subbloq=MultiCon ...\n", + "\n", + "\n", + "\n", + "b34->b85\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b86\n", + "\n", + "HasDuplicates†\n", + "subbloq=HasDupli ...\n", + "\n", + "\n", + "\n", + "b34->b86\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b87\n", + "\n", + "SortInPlace†\n", + "subbloq=SortInPl ...\n", + "\n", + "\n", + "\n", + "b34->b87\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b88\n", + "\n", + "SimpleGuidingState†\n", + "subbloq=SimpleGu ...\n", + "\n", + "\n", + "\n", + "b34->b88\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b89\n", + "\n", + "ProbabilisticUncompute†\n", + "subbloq=Probabil ...\n", + "\n", + "\n", + "\n", + "b34->b89\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b34->b130\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b90\n", + "\n", + "And\n", + "cv1=1, cv2=1, uncompute=False\n", + "\n", + "\n", + "\n", + "b35->b90\n", + "\n", + "\n", + "147\n", + "\n", + "\n", + "\n", + "b91\n", + "\n", + "And†\n", + "cv1=1, cv2=1, uncompute=True\n", + "\n", + "\n", + "\n", + "b36->b91\n", + "\n", + "\n", + "147\n", + "\n", + "\n", + "\n", + "b92\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=3, phase_bitsize=7, rom_values=(64, 64, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b92\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b93\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=4, phase_bitsize=7, rom_values=(64, 64, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b93\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b94\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=7, phase_bitsize=7, rom_values=(48, 48, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b94\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b95\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=6, phase_bitsize=7, rom_values=(64, 63, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b95\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b37->b96\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b37->b97\n", + "\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "b98\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=2, phase_bitsize=7, rom_values=(64, 62, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b98\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b99\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=1, phase_bitsize=7, rom_values=(64, 0), control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b99\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b100\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=5, phase_bitsize=7, rom_values=(64, 64, ..., control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b100\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b101\n", + "\n", + "PRGAViaPhaseGradient\n", + "selection_bitsize=0, phase_bitsize=7, rom_values=(33,), control_bitsize=1\n", + "\n", + "\n", + "\n", + "b37->b101\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b37->b130\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b102\n", + "\n", + "CZPowGate\n", + "exponent=-0.25, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b39->b102\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b103\n", + "\n", + "CZPowGate\n", + "exponent=-0.125, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b39->b103\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b104\n", + "\n", + "CZPowGate\n", + "exponent=-0.0625, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b39->b104\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b105\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=2.5e-11\n", + "\n", + "\n", + "\n", + "b39->b105\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b106\n", + "\n", + "CZPowGate\n", + "exponent=-0.25, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b40->b106\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b107\n", + "\n", + "CZPowGate\n", + "exponent=-0.125, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b40->b107\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b108\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=3.333333 ...\n", + "\n", + "\n", + "\n", + "b40->b108\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b109\n", + "\n", + "CZPowGate\n", + "exponent=-0.125, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b41->b109\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b110\n", + "\n", + "CZPowGate\n", + "exponent=-0.0625, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b41->b110\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b111\n", + "\n", + "CZPowGate\n", + "exponent=-0.25, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b41->b111\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b112\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b41->b112\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b113\n", + "\n", + "CZPowGate\n", + "exponent=-0.03125, global_shift=0.0, eps=2.000000 ...\n", + "\n", + "\n", + "\n", + "b41->b113\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b114\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=1e-10\n", + "\n", + "\n", + "\n", + "b42->b114\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b115\n", + "\n", + "CZPowGate\n", + "exponent=-0.25, global_shift=0.0, eps=5e-11\n", + "\n", + "\n", + "\n", + "b43->b115\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b116\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=5e-11\n", + "\n", + "\n", + "\n", + "b43->b116\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b117\n", + "\n", + "CZPowGate\n", + "exponent=-0.015625, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b117\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b118\n", + "\n", + "CZPowGate\n", + "exponent=-0.0625, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b118\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b119\n", + "\n", + "CZPowGate\n", + "exponent=-0.125, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b119\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b120\n", + "\n", + "CZPowGate\n", + "exponent=-0.25, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b120\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b121\n", + "\n", + "CZPowGate\n", + "exponent=-0.03125, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b121\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b122\n", + "\n", + "CZPowGate\n", + "exponent=-0.5, global_shift=0.0, eps=1.666666 ...\n", + "\n", + "\n", + "\n", + "b44->b122\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b46->b123\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b124\n", + "\n", + "GPhase\n", + "exponent=1.0, eps=1e-11\n", + "\n", + "\n", + "\n", + "b47->b124\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b47->b125\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b47->b127\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b47->b128\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b47->b130\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b49->b125\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b126\n", + "\n", + "ZPowGate\n", + "exponent=1.0, global_shift=0.0, eps=1e-11\n", + "\n", + "\n", + "\n", + "b49->b126\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b49->b127\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b49->b128\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b50->b129\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b55->b130\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b131\n", + "\n", + "CCX\n", + "cvs=(0, 0), target_gate=cirq.X\n", + "\n", + "\n", + "\n", + "b55->b131\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b132\n", + "\n", + "HasDuplicates\n", + "l=8, dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b55->b132\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b133\n", + "\n", + "SimpleGuidingState\n", + "inst=KXorInst ..., phasegrad_bitsize=30\n", + "\n", + "\n", + "\n", + "b55->b133\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "b134\n", + "\n", + "ProbabilisticUncompute\n", + "bitsize=24\n", + "\n", + "\n", + "\n", + "b55->b134\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "b135\n", + "\n", + "SortInPlace\n", + "l=8, dtype=QUInt(bi ...\n", + "\n", + "\n", + "\n", + "b55->b135\n", + "\n", + "\n", + "1\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g, sigma = bloq.call_graph(max_depth=6)\n", + "show_call_graph(g)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "234316cd-b790-42ee-baef-d9f08bf2e19d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'toffoli': 12261972437984,\n", + " 'cswap': 5171509416110263,\n", + " 'and_bloq': 255058640186620068,\n", + " 'clifford': 373887003261338334,\n", + " 'rotation': 11450518379588*\\tilde{O}(350784) + 17649125770113,\n", + " 'measurement': 255058640186620068}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qualtran.resource_counting import get_cost_value, QECGatesCost\n", + "\n", + "gc = get_cost_value(bloq, QECGatesCost())\n", + "gc.asdict()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "a3af760a-c97f-4731-b1d0-5749271f0cdb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'toffoli': 0.0158706515045947,\n", + " 'cswap': 6.69347644605456,\n", + " 'and_bloq': 330.121994003046,\n", + " 'clifford': 483.921356116957,\n", + " 'rotation': 0.0148203878020848*\\tilde{O}(350784) + 0.0228432355295913,\n", + " 'measurement': 330.121994003046}" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(gc * (1/cost_O_tilde)).asdict()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "493750c7-2cd2-40ef-a8b1-f5b52c2a5792", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'7.726193e+14'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f\"{cost_O_tilde:e}\"" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/mcmt/and_bloq.py b/qualtran/bloqs/mcmt/and_bloq.py index e1da7fff5..f501df9e0 100644 --- a/qualtran/bloqs/mcmt/and_bloq.py +++ b/qualtran/bloqs/mcmt/and_bloq.py @@ -69,7 +69,7 @@ import quimb.tensor as qtn # TODO: https://github.com/quantumlib/Qualtran/issues/1346 -FLAG_AND_AS_LEAF = False +FLAG_AND_AS_LEAF = True @frozen diff --git a/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control.py b/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control.py new file mode 100644 index 000000000..150def592 --- /dev/null +++ b/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control.py @@ -0,0 +1,87 @@ +# 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, Iterable, Optional, Protocol, runtime_checkable, Sequence, TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from qualtran import AddControlledT, Bloq, BloqBuilder, CtrlSpec, SoquetT + + +@runtime_checkable +class BloqWithSpecializedControl(Protocol): + """mixin for a bloq that has a specialized single qubit controlled version.""" + + @property + def cv(self) -> Optional[int]: ... + + def with_cv(self, *, cv: Optional[int]) -> 'Bloq': ... + + @property + def ctrl_reg_name(self) -> str: ... + + +def get_ctrl_system_for_bloq_with_specialized_single_qubit_control( + bloq: 'BloqWithSpecializedControl', ctrl_spec: 'CtrlSpec' +) -> tuple['Bloq', 'AddControlledT']: + from qualtran import Bloq, CtrlSpec, Soquet + from qualtran.bloqs.mcmt import ControlledViaAnd + + if ctrl_spec != CtrlSpec(): + assert isinstance(bloq, Bloq) + return ControlledViaAnd.make_ctrl_system(bloq=bloq, ctrl_spec=ctrl_spec) + + assert isinstance( + bloq, BloqWithSpecializedControl + ), f"{bloq} must implement protocol {BloqWithSpecializedControl}" + + if bloq.cv is None: + # the easy case: use the controlled bloq + ctrl_bloq = bloq.with_cv(cv=1) + ctrl_reg_name = bloq.ctrl_reg_name + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + in_soqs |= {ctrl_reg_name: ctrl} + + out_soqs = bb.add_d(ctrl_bloq, **in_soqs) + + ctrl = out_soqs.pop(ctrl_reg_name) + return [ctrl], out_soqs.values() + + else: + # the difficult case: must combine the two controls into one + un_ctrl_bloq = bloq.with_cv(cv=None) + ctrl_bloq = ControlledViaAnd(un_ctrl_bloq, CtrlSpec(cvs=[1, bloq.cv])) + + def _adder( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT'] + ) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + # extract the two control bits + (ctrl0,) = ctrl_soqs + ctrl1 = in_soqs.pop('ctrl') + + ctrl0 = cast(Soquet, ctrl0) + ctrl1 = cast(Soquet, ctrl1) + + # add the singly controlled bloq + ctrls, *out_soqs = bb.add_t(ctrl_bloq, ctrl=[ctrl0, ctrl1], **in_soqs) + assert isinstance(ctrls, np.ndarray) + ctrl0, ctrl1 = ctrls + + return [ctrl0], [ctrl1, *out_soqs] + + return ctrl_bloq, _adder diff --git a/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control_test.py b/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control_test.py new file mode 100644 index 000000000..7f3eb73e6 --- /dev/null +++ b/qualtran/bloqs/mcmt/bloq_with_specialized_single_qubit_control_test.py @@ -0,0 +1,110 @@ +# 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. +# +# 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 Optional, Sequence, Tuple +from unittest.mock import ANY + +import pytest +from attrs import evolve, frozen + +from qualtran import AddControlledT, Bloq, CtrlSpec, Signature +from qualtran.bloqs.mcmt.bloq_with_specialized_single_qubit_control import ( + get_ctrl_system_for_bloq_with_specialized_single_qubit_control, +) +from qualtran.resource_counting import CostKey, GateCounts, get_cost_value, QECGatesCost + + +@frozen +class AtomWithSpecializedControl(Bloq): + cv: Optional[int] = None + + @property + def signature(self) -> 'Signature': + n_ctrl = 1 if self.cv is not None else 0 + return Signature.build(ctrl=n_ctrl, q=2) + + def with_cv(self, *, cv: Optional[int]) -> Bloq: + return evolve(self, cv=cv) + + @property + def ctrl_reg_name(self) -> str: + return 'ctrl' + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + return get_ctrl_system_for_bloq_with_specialized_single_qubit_control(self, ctrl_spec) + + @staticmethod + def cost_expr_for_cv(cv: Optional[int]): + import sympy + + c_unctrl = sympy.Symbol("_c_target") + c_ctrl = sympy.Symbol("_c_ctrl_") + + if cv is None: + return c_unctrl + return c_unctrl + c_ctrl + + def my_static_costs(self, cost_key: 'CostKey'): + if cost_key == QECGatesCost(): + r = self.cost_expr_for_cv(self.cv) + return GateCounts(rotation=r) + + return NotImplemented + + +def ON(n: int = 1) -> CtrlSpec: + return CtrlSpec(cvs=[1] * n) + + +def OFF(n: int = 1) -> CtrlSpec: + return CtrlSpec(cvs=[0] * n) + + +@pytest.mark.parametrize( + 'ctrl_specs', + [ + [ON()], + [OFF()], + [OFF(), OFF()], + [OFF(4)], + [OFF(2), OFF(2)], + [ON(), OFF(5)], + [ON(), ON(), ON()], + [OFF(4), ON(3), OFF(5)], + ], +) +def test_custom_controlled(ctrl_specs: Sequence[CtrlSpec]): + bloq: Bloq = AtomWithSpecializedControl() + for ctrl_spec in ctrl_specs: + bloq = bloq.controlled(ctrl_spec) + n_ctrls = sum(ctrl_spec.num_qubits for ctrl_spec in ctrl_specs) + + gc = get_cost_value(bloq, QECGatesCost()) + assert gc == GateCounts( + and_bloq=n_ctrls - 1, + rotation=AtomWithSpecializedControl.cost_expr_for_cv(1), + clifford=ANY, + measurement=ANY, + ) diff --git a/qualtran/bloqs/mcmt/multi_control_pauli.py b/qualtran/bloqs/mcmt/multi_control_pauli.py index 27300e739..fea7cf547 100644 --- a/qualtran/bloqs/mcmt/multi_control_pauli.py +++ b/qualtran/bloqs/mcmt/multi_control_pauli.py @@ -111,7 +111,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str def __str__(self) -> str: n = self.n_ctrls - ctrl = f'C^{n}' if is_symbolic(n) or n > 2 else ['', 'C', 'CC'][int(n)] + ctrl = f'C[{n}]' if is_symbolic(n) or n > 2 else ['', 'C', 'CC'][int(n)] return f'{ctrl}{self.target_gate!s}' def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: @@ -192,6 +192,9 @@ class MultiControlX(MultiControlPauli): def _X(self): return cirq.X + def adjoint(self) -> 'Bloq': + return self + @frozen class MultiControlZ(MultiControlPauli): @@ -205,3 +208,6 @@ class MultiControlZ(MultiControlPauli): @target_gate.default def _Z(self): return cirq.Z + + def adjoint(self) -> 'Bloq': + return self diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state.py b/qualtran/bloqs/phase_estimation/lp_resource_state.py index 53cadba44..f58776ac0 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state.py @@ -140,8 +140,9 @@ def from_standard_deviation_eps(cls, eps: SymbolicFloat) -> 'LPResourceState': def m_bits(self) -> SymbolicInt: return self.bitsize - def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']: - qpe_reg = bb.allocate(dtype=self.m_register.dtype) + def build_composite_bloq( + self, bb: 'BloqBuilder', qpe_reg: 'Soquet', **soqs: 'SoquetT' + ) -> Dict[str, 'SoquetT']: anc, flag = bb.allocate(dtype=QBit()), bb.allocate(dtype=QBit()) flag_angle = np.arccos(1 / (1 + 2**self.bitsize)) diff --git a/qualtran/bloqs/phase_estimation/qpe_window_state.py b/qualtran/bloqs/phase_estimation/qpe_window_state.py index 5d6f0c373..eea507d2e 100644 --- a/qualtran/bloqs/phase_estimation/qpe_window_state.py +++ b/qualtran/bloqs/phase_estimation/qpe_window_state.py @@ -17,7 +17,7 @@ import attrs -from qualtran import Bloq, bloq_example, BloqDocSpec, QFxp, Register, Side, Signature +from qualtran import Bloq, bloq_example, BloqDocSpec, QFxp, Register, Signature from qualtran.bloqs.basic_gates import Hadamard, OnEach from qualtran.symbolics import ceil, log2, pi, SymbolicFloat, SymbolicInt @@ -31,7 +31,7 @@ class QPEWindowStateBase(Bloq, metaclass=abc.ABCMeta): @cached_property def m_register(self) -> 'Register': - return Register('qpe_reg', QFxp(self.m_bits, self.m_bits), side=Side.RIGHT) + return Register('qpe_reg', QFxp(self.m_bits, self.m_bits)) @property @abc.abstractmethod @@ -95,8 +95,7 @@ def from_standard_deviation_eps(cls, eps: SymbolicFloat): """ return cls(ceil(2 * log2(pi(eps) / eps))) - def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: - qpe_reg = bb.allocate(dtype=self.m_register.dtype) + def build_composite_bloq(self, bb: 'BloqBuilder', qpe_reg) -> Dict[str, 'SoquetT']: qpe_reg = bb.add(OnEach(self.m_bits, Hadamard()), q=qpe_reg) return {'qpe_reg': qpe_reg} diff --git a/qualtran/bloqs/state_preparation/black_box_prepare.py b/qualtran/bloqs/state_preparation/black_box_prepare.py index 42aacba78..892db1735 100644 --- a/qualtran/bloqs/state_preparation/black_box_prepare.py +++ b/qualtran/bloqs/state_preparation/black_box_prepare.py @@ -29,8 +29,7 @@ ) from qualtran.bloqs.bookkeeping.auto_partition import AutoPartition from qualtran.bloqs.state_preparation.prepare_base import PrepareOracle -from qualtran.symbolics import ssum, SymbolicFloat, SymbolicInt -from qualtran.symbolics.types import is_symbolic +from qualtran.symbolics import is_zero, ssum, SymbolicFloat, SymbolicInt @frozen @@ -75,19 +74,19 @@ def signature(self) -> Signature: return Signature.build(selection=self.selection_bitsize, junk=self.junk_bitsize) def build_composite_bloq(self, bb: BloqBuilder, **soqs: SoquetT) -> Dict[str, SoquetT]: - if self.selection_bitsize == 0: + if is_zero(self.selection_bitsize): return soqs partitions = [ (self.selection_registers[0], [r.name for r in self.prepare.selection_registers]) ] - if is_symbolic(self.junk_bitsize) or self.junk_bitsize > 0: + if not is_zero(self.junk_bitsize): partitions.append( (self.junk_registers[0], [r.name for r in self.prepare.junk_registers]) ) return bb.add_d(AutoPartition(self.prepare, partitions), **soqs) def __str__(self) -> str: - return 'Prep' + return f'BBPrepare[{self.prepare}]' @bloq_example 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 diff --git a/qualtran/drawing/graphviz.py b/qualtran/drawing/graphviz.py index b6e70050d..12835098e 100644 --- a/qualtran/drawing/graphviz.py +++ b/qualtran/drawing/graphviz.py @@ -402,6 +402,24 @@ def cxn_edge(self, left_id: str, right_id: str, cxn: Connection) -> pydot.Edge: arrowsize=0.25, ) + def cxn_label(self, cxn: Connection) -> str: + import sympy + + from qualtran.symbolics import is_symbolic + + n = cxn.shape + if not is_symbolic(n): + return str(n) + + label = sympy.printing.pretty(n) + label_lines = label.split('\n') + if len(label_lines) > 1: + return str(n)[:10] + " ..." + + if len(label) > 15: + return label[:15] + " ..." + return label + class TypedGraphDrawer(PrettyGraphDrawer): @staticmethod diff --git a/qualtran/symbolics/simplification.py b/qualtran/symbolics/simplification.py new file mode 100644 index 000000000..6c1cfb1ad --- /dev/null +++ b/qualtran/symbolics/simplification.py @@ -0,0 +1,23 @@ +# 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 qualtran.symbolics.types import SymbolicInt + + +def extract_int(x: SymbolicInt) -> SymbolicInt: + """Extract a raw python int if the input is sympy.Integer, otherwise return as-is.""" + try: + result = int(x) + return result + except TypeError: + return x