diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 539c5ff82..ced5e1343 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -625,7 +625,8 @@ title='Textbook Quantum Phase Estimation', module=qualtran.bloqs.phase_estimation.text_book_qpe, bloq_specs=[ - qualtran.bloqs.phase_estimation.text_book_qpe._CC_TEXTBOOK_PHASE_ESTIMATION_DOC + qualtran.bloqs.phase_estimation.qpe_window_state._CC_RECTANGULAR_WINDOW_STATE_DOC, + qualtran.bloqs.phase_estimation.text_book_qpe._CC_TEXTBOOK_PHASE_ESTIMATION_DOC, ], ), NotebookSpecV2( diff --git a/qualtran/bloqs/phase_estimation/__init__.py b/qualtran/bloqs/phase_estimation/__init__.py index 09c0ebd80..adf05871f 100644 --- a/qualtran/bloqs/phase_estimation/__init__.py +++ b/qualtran/bloqs/phase_estimation/__init__.py @@ -13,5 +13,6 @@ # limitations under the License. from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState +from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState from qualtran.bloqs.phase_estimation.qubitization_qpe import QubitizationQPE from qualtran.bloqs.phase_estimation.text_book_qpe import TextbookQPE diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state.py b/qualtran/bloqs/phase_estimation/lp_resource_state.py index c283b221f..89d3906f5 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state.py @@ -13,31 +13,23 @@ # limitations under the License. """Resource states proposed by A. Luis and J. Peřina (1996) for optimal phase measurements""" +from collections import Counter from functools import cached_property -from typing import Iterator, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, Set, TYPE_CHECKING import attrs -import cirq import numpy as np import sympy -from numpy.typing import NDArray - -from qualtran import ( - Bloq, - bloq_example, - BloqDocSpec, - GateWithRegisters, - QUInt, - Register, - Side, - Signature, -) -from qualtran.bloqs.basic_gates import CZPowGate, GlobalPhase, Hadamard, OnEach, Ry, Rz, XGate -from qualtran.bloqs.mcmt import MultiControlZ + +from qualtran import Bloq, bloq_example, BloqDocSpec, GateWithRegisters, QBit, Signature +from qualtran.bloqs.basic_gates import CZ, Hadamard, OnEach, Ry, Rz, XGate +from qualtran.bloqs.phase_estimation.qpe_window_state import QPEWindowStateBase +from qualtran.bloqs.reflections.reflection_using_prepare import ReflectionUsingPrepare from qualtran.cirq_interop.t_complexity_protocol import TComplexity -from qualtran.symbolics import acos, HasLength, is_symbolic, pi, SymbolicInt +from qualtran.symbolics import acos, ceil, is_symbolic, log2, pi, SymbolicFloat, SymbolicInt if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet, SoquetT from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -67,32 +59,33 @@ def signature(self) -> 'Signature': def pretty_name(self) -> str: return 'LPRS' - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] # type: ignore[type-var] - ) -> Iterator[cirq.OP_TREE]: + def build_composite_bloq( + self, bb: 'BloqBuilder', *, m: 'SoquetT', anc: 'Soquet' + ) -> Dict[str, 'SoquetT']: if isinstance(self.bitsize, sympy.Expr): raise ValueError(f'Symbolic bitsize {self.bitsize} not supported') - q, anc = quregs['m'].tolist()[::-1], quregs['anc'] - yield [OnEach(self.bitsize, Hadamard()).on(*q), Hadamard().on(*anc)] + m = bb.add(OnEach(self.bitsize, Hadamard()), q=m) + q = bb.split(m)[::-1] + anc = bb.add(Hadamard(), q=anc) for i in range(self.bitsize): rz_angle = -2 * np.pi * (2**i) / (2**self.bitsize + 1) - yield Rz(angle=rz_angle).controlled().on(q[i], *anc) - yield Rz(angle=-2 * np.pi / (2**self.bitsize + 1)).on(*anc) - yield Hadamard().on(*anc) + q[i], anc = bb.add(Rz(angle=rz_angle).controlled(), ctrl=q[i], q=anc) + anc = bb.add(Rz(angle=-2 * np.pi / (2**self.bitsize + 1)), q=anc) + anc = bb.add(Hadamard(), q=anc) + return {'m': bb.join(q[::-1]), 'anc': anc} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: rz_angle = -2 * pi(self.bitsize) / (2**self.bitsize + 1) - ret: Set[Tuple[Bloq, SymbolicInt]] = { - (Rz(angle=rz_angle), 1), - (Hadamard(), 2 + self.bitsize), - } + ret: Counter['Bloq'] = Counter() + ret[Rz(angle=rz_angle)] += 1 + ret[OnEach(self.bitsize, Hadamard())] += 1 + ret[Hadamard()] += 2 if is_symbolic(self.bitsize): - ret |= {(Rz(angle=rz_angle).controlled(), self.bitsize)} + ret[Rz(angle=rz_angle).controlled()] += self.bitsize else: - ret |= { - (Rz(angle=rz_angle * (2**i)).controlled(), 1) for i in range(int(self.bitsize)) - } - return ret + for i in range(self.bitsize): + ret[Rz(angle=rz_angle * (2**i)).controlled()] += 1 + return set(ret.items()) def _t_complexity_(self) -> 'TComplexity': # Uses self.bitsize controlled-Rz rotations which decomposes into @@ -102,7 +95,7 @@ def _t_complexity_(self) -> 'TComplexity': @attrs.frozen -class LPResourceState(GateWithRegisters): +class LPResourceState(QPEWindowStateBase): r"""Prepares optimal resource state $\chi_{m}$ proposed by A. Luis and J. Peřina (1996) Uses a single round of amplitude amplification, as described in Ref 2, to prepare the @@ -128,53 +121,77 @@ class LPResourceState(GateWithRegisters): @cached_property def signature(self) -> 'Signature': - return Signature([Register('m', QUInt(self.bitsize), side=Side.THRU)]) + return Signature([self.m_register]) + + @classmethod + def from_standard_deviation_eps(cls, eps: SymbolicFloat) -> 'LPResourceState': + r"""Estimate the phase $\phi$ with uncertainty in standard deviation bounded by $\epsilon$. + + The standard deviation of phase estimation using optimal resource states scales as the + square of Holevo variance $\tan{\frac{\pi}{2^m}}$. + This bound can be used to estimate the size of the phase register s.t. the estimated phase + has a standard deviation of at-most $\epsilon$. See the class docstring for more details. - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> Iterator[cirq.OP_TREE]: - """Use the _LPResourceStateHelper and do a single round of amplitude amplification.""" - q = quregs['m'].flatten().tolist() - anc, flag = context.qubit_manager.qalloc(2) + $$ + m = \lceil\log_2{\pi/\epsilon}\rceil + $$ + + Args: + eps: Maximum standard deviation of the estimated phase. + """ + return LPResourceState(ceil(log2(pi(eps) / eps))) + + @property + 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) + anc, flag = bb.allocate(dtype=QBit()), bb.allocate(dtype=QBit()) flag_angle = np.arccos(1 / (1 + 2**self.bitsize)) # Prepare initial state - yield Ry(angle=flag_angle).on(flag) - yield LPRSInterimPrep(self.bitsize).on(*q, anc) + flag = bb.add(Ry(angle=flag_angle), q=flag) + qpe_reg, anc = bb.add(LPRSInterimPrep(self.bitsize), m=qpe_reg, anc=anc) # Reflect around the target state - yield CZPowGate().on(flag, anc) + flag, anc = bb.add(CZ(), q1=flag, q2=anc) # Reflect around the initial state - yield LPRSInterimPrep(self.bitsize).adjoint().on(*q, anc) - yield Ry(angle=-flag_angle).on(flag) - - yield XGate().on(flag) - yield MultiControlZ((0,) * (self.bitsize + 1)).on(*q, anc, flag) - yield XGate().on(flag) + qpe_reg, anc = bb.add(LPRSInterimPrep(self.bitsize).adjoint(), m=qpe_reg, anc=anc) + flag = bb.add(Ry(angle=-flag_angle), q=flag) + + flag, anc, qpe_reg = bb.add( + ReflectionUsingPrepare.reflection_around_zero([1, 1, self.bitsize], global_phase=1j), + reg0_=flag, + reg1_=anc, + reg2_=qpe_reg, + ) - yield LPRSInterimPrep(self.bitsize).on(*q, anc) - yield Ry(angle=flag_angle).on(flag) + qpe_reg, anc = bb.add(LPRSInterimPrep(self.bitsize), m=qpe_reg, anc=anc) + flag = bb.add(Ry(angle=flag_angle), q=flag) # Reset ancilla to |0> state. - yield [XGate().on(flag), XGate().on(anc)] - yield GlobalPhase(exponent=0.5).on() - context.qubit_manager.qfree([flag, anc]) + flag = bb.add(XGate(), q=flag) + anc = bb.add(XGate(), q=anc) + bb.free(flag) + bb.free(anc) + return {'qpe_reg': qpe_reg} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: flag_angle = acos(1 / (1 + 2**self.bitsize)) - cvs: Union[HasLength, Tuple[int, ...]] = ( - HasLength(self.bitsize + 1) if is_symbolic(self.bitsize) else (0,) * (self.bitsize + 1) + reflection_bloq: 'Bloq' = ReflectionUsingPrepare.reflection_around_zero( + [1, 1, self.bitsize], global_phase=1j ) return { (LPRSInterimPrep(self.bitsize), 2), (LPRSInterimPrep(self.bitsize).adjoint(), 1), - (Ry(angle=flag_angle), 3), - (MultiControlZ(cvs), 1), - (XGate(), 4), - (GlobalPhase(exponent=0.5), 1), - (CZPowGate(), 1), + (Ry(angle=flag_angle), 2), + (Ry(angle=-1 * flag_angle), 1), + (reflection_bloq, 1), + (XGate(), 2), + (CZ(), 1), } diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py index 2e374d929..2d04f73dd 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py @@ -23,7 +23,6 @@ LPRSInterimPrep, ) from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity -from qualtran.cirq_interop.testing import GateHelper from qualtran.resource_counting.generalizers import ( generalize_rotation_angle, ignore_alloc_free, @@ -42,15 +41,15 @@ def test_lp_resource_state_auto(bloq_autotester): def test_lp_resource_state_symb(): bloq = _lp_resource_state_symbolic.make() - assert bloq.t_complexity().t == 4 * bloq.bitsize + assert bloq.t_complexity().t == 4 * bloq.bitsize + 4 def get_interim_resource_state(m: int) -> np.ndarray: N = 2**m - state_vector = np.zeros(2 * N, dtype=np.complex128) - state_vector[:N] = np.cos(np.pi * (1 + np.arange(N)) / (1 + N)) - state_vector[N:] = 1j * np.sin(np.pi * (1 + np.arange(N)) / (1 + N)) - return np.sqrt(1 / N) * state_vector + state_vector = np.zeros((N, 2), dtype=np.complex128) + state_vector[:, 0] = np.cos(np.pi * (1 + np.arange(N)) / (1 + N)) + state_vector[:, 1] = 1j * np.sin(np.pi * (1 + np.arange(N)) / (1 + N)) + return np.sqrt(1 / N) * state_vector.reshape(2 * N) def get_resource_state(m: int) -> np.ndarray: @@ -58,72 +57,37 @@ def get_resource_state(m: int) -> np.ndarray: return np.sqrt(2 / (1 + N)) * np.sin(np.pi * (1 + np.arange(N)) / (1 + N)) -def test_intermediate_resource_state_cirq_quick(): - n = 3 - bloq = LPRSInterimPrep(n) - state = GateHelper(bloq).circuit.final_state_vector() - np.testing.assert_allclose(state, get_interim_resource_state(n)) - - def test_intermediate_resource_state_tensor_quick(): n = 3 bloq = LPRSInterimPrep(n) state_prep = initialize_from_zero(bloq) state_vec = state_prep.tensor_contract() - pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1068") np.testing.assert_allclose(state_vec, get_interim_resource_state(n)) @pytest.mark.slow @pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_intermediate_resource_state_cirq(n): +def test_intermediate_resource_state(n): bloq = LPRSInterimPrep(n) - state = GateHelper(bloq).circuit.final_state_vector() + state = initialize_from_zero(bloq).tensor_contract() np.testing.assert_allclose(state, get_interim_resource_state(n)) -@pytest.mark.slow -@pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_intermediate_resource_state_tensor(n): - bloq = LPRSInterimPrep(n) - state_prep = initialize_from_zero(bloq) - state_vec = state_prep.tensor_contract() - pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1068") - np.testing.assert_allclose(state_vec, get_interim_resource_state(n)) - - -def test_prepares_resource_state_cirq_quick(): +def test_prepares_resource_state_quick(): n = 3 bloq = LPResourceState(n) - state = GateHelper(bloq).circuit.final_state_vector() + state = bloq.tensor_contract() np.testing.assert_allclose(state, get_resource_state(n)) -def test_prepares_resource_state_tensor_quick(): - n = 3 - bloq = LPResourceState(n) - state_prep = initialize_from_zero(bloq) - state_vec = state_prep.tensor_contract() - np.testing.assert_allclose(state_vec, get_resource_state(n)) - - @pytest.mark.slow @pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_prepares_resource_state_cirq(n): +def test_prepares_resource_state(n): bloq = LPResourceState(n) - state = GateHelper(bloq).circuit.final_state_vector() + state = bloq.tensor_contract() np.testing.assert_allclose(state, get_resource_state(n)) -@pytest.mark.slow -@pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_prepares_resource_state_tensor(n): - bloq = LPResourceState(n) - state_prep = initialize_from_zero(bloq) - state_vec = state_prep.tensor_contract() - np.testing.assert_allclose(state_vec, get_resource_state(n)) - - @pytest.mark.parametrize('n', [*range(1, 14, 2)]) def test_t_complexity(n): bloq = LPResourceState(n) @@ -131,10 +95,10 @@ def test_t_complexity(n): bloq, [ignore_split_join, ignore_alloc_free, generalize_rotation_angle] ) lprs_interim_count = 3 * TComplexity(rotations=2 * n + 1, clifford=2 + 3 * n) - multi_control_pauli_count = TComplexity(t=4 * n, clifford=17 * n + 5) + reflection_using_prepare = TComplexity(t=4 * n + 4, clifford=17 * n + 22) misc_count = TComplexity(rotations=3, clifford=5) - assert bloq.t_complexity() == (lprs_interim_count + multi_control_pauli_count + misc_count) + assert bloq.t_complexity() == (lprs_interim_count + reflection_using_prepare + misc_count) @pytest.mark.parametrize('bitsize', [*range(1, 14, 2)]) diff --git a/qualtran/bloqs/phase_estimation/qpe_window_state.py b/qualtran/bloqs/phase_estimation/qpe_window_state.py new file mode 100644 index 000000000..8b92a6495 --- /dev/null +++ b/qualtran/bloqs/phase_estimation/qpe_window_state.py @@ -0,0 +1,123 @@ +# 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 functools import cached_property +from typing import Dict, TYPE_CHECKING + +import attrs + +from qualtran import Bloq, bloq_example, BloqDocSpec, QFxp, Register, Side, Signature +from qualtran.bloqs.basic_gates import Hadamard, OnEach +from qualtran.symbolics import ceil, log2, pi, SymbolicFloat, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, SoquetT + + +@attrs.frozen +class QPEWindowStateBase(Bloq, metaclass=abc.ABCMeta): + """Base class to construct window states""" + + @cached_property + def m_register(self) -> 'Register': + return Register('qpe_reg', QFxp(self.m_bits, self.m_bits), side=Side.RIGHT) + + @property + @abc.abstractmethod + def m_bits(self) -> SymbolicInt: + ... + + +@attrs.frozen +class RectangularWindowState(QPEWindowStateBase): + """Window state used in Textbook version of QPE. Applies Hadamard on all qubits. + + Args: + bitsize: Size of the control register to prepare window state on. + + Registers: + qpe_reg: A `bitsize` sized RIGHT register. + """ + + bitsize: SymbolicInt + + @property + def m_bits(self) -> SymbolicInt: + return self.bitsize + + @cached_property + def signature(self) -> 'Signature': + return Signature([self.m_register]) + + @classmethod + def from_precision_and_delta(cls, precision: SymbolicInt, delta: SymbolicFloat): + r"""Estimate $\varphi$ to $precision$ bits with $1-\delta$ success probability. + + Uses Eq 5.35 from Neilson and Chuang to estimate the size of phase register s.t. we can + estimate the phase $\varphi$ to $precision$ bits of accuracy with probability at least + $1 - \delta$. See the class docstring of `TextbookQPE` bloq for more details. + + ``` + m = n + ceil(log2(2 + 1/(2*delta))) + ``` + + Args: + precision: Number of bits of precision + delta: Probability of success. + """ + return cls(precision + ceil(log2(2 + 1 / (2 * delta)))) + + @classmethod + def from_standard_deviation_eps(cls, eps: SymbolicFloat): + r"""Estimate the phase $\phi$ with uncertainty in standard deviation bounded by $\epsilon$. + + The standard deviation of textbook phase estimation scales as $\frac{2\pi}{\sqrt{2^{m}}}$. + This bound can be used to estimate the size of the phase register s.t. the estimated phase + has a standard deviation of at-most $\epsilon$. See the class docstring of `TextbookQPE` + bloq for more details. + + ``` + m = ceil(2*log2(pi/eps)) + ``` + + Args: + eps: Maximum standard deviation of the estimated phase. + """ + 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) + qpe_reg = bb.add(OnEach(self.m_bits, Hadamard()), q=qpe_reg) + return {'qpe_reg': qpe_reg} + + +@bloq_example +def _rectangular_window_state_small() -> RectangularWindowState: + rectangular_window_state_small = RectangularWindowState(5) + return rectangular_window_state_small + + +@bloq_example +def _rectangular_window_state_symbolic() -> RectangularWindowState: + import sympy + + rectangular_window_state_symbolic = RectangularWindowState(sympy.Symbol('n')) + return rectangular_window_state_symbolic + + +_CC_RECTANGULAR_WINDOW_STATE_DOC = BloqDocSpec( + bloq_cls=RectangularWindowState, + import_line='from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState', + examples=(_rectangular_window_state_small, _rectangular_window_state_symbolic), +) diff --git a/qualtran/bloqs/phase_estimation/qpe_window_state_test.py b/qualtran/bloqs/phase_estimation/qpe_window_state_test.py new file mode 100644 index 000000000..0ca33cf4a --- /dev/null +++ b/qualtran/bloqs/phase_estimation/qpe_window_state_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 + +from qualtran.bloqs.phase_estimation.qpe_window_state import ( + _rectangular_window_state_small, + _rectangular_window_state_symbolic, + RectangularWindowState, +) + + +def test_rectangular_window_state_tensor(): + n = 4 + bloq = RectangularWindowState(n) + np.testing.assert_allclose(bloq.tensor_contract(), np.zeros(2**n) + 1 / 2 ** (n / 2)) + + +def test_rectangular_window_state(bloq_autotester): + bloq_autotester(_rectangular_window_state_small) + bloq_autotester(_rectangular_window_state_symbolic) diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb b/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb index b75540c5d..92ab548b9 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe.ipynb @@ -42,7 +42,7 @@ "for learning eigenphases of the `walk` operator with `m` bits of accuracy. The\n", "circuit is implemented as given in Fig.2 of Ref-1.\n", "\n", - " ```\n", + "```\n", " ┌─────────┐ ┌─────────┐\n", " |0> -│ │-------------------------(0)---(0)---│ │---M--- [m1]:highest bit\n", " │ │ | | │ │\n", @@ -53,7 +53,8 @@ " |0> -│ │---@----+-----+--+-----+--+-----+----│ │---M--- [m4]:lowest bit\n", " └─────────┘ | | | | | | | └─────────┘\n", "|Psi> ---------------W----R-W^2-R--R-W^4-R--R-W^8-R---------------------- |Psi>\n", - " ```\n", + "\n", + "```\n", "\n", "TODO: Note that there are slight differences between the Fig2 of the Ref[1] and the circuit\n", " implemented here. Further investigation is required to reconcile the difference.\n", @@ -108,7 +109,7 @@ "from qualtran.bloqs.chemistry.hubbard_model.qubitization import (\n", " get_walk_operator_for_hubbard_model,\n", ")\n", - "from qualtran.bloqs.phase_estimation import QubitizationQPE\n", + "from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE\n", "\n", "x_dim, y_dim, t = 2, 2, 2\n", "u = 4 * t\n", @@ -118,8 +119,8 @@ "N = x_dim * y_dim * 2\n", "qlambda = 2 * N * t + (N * u) // 2\n", "qpe_eps = algo_eps / (qlambda * np.sqrt(2))\n", - "qubitization_qpe_hubbard_model_small = QubitizationQPE.from_standard_deviation_eps(\n", - " walk, qpe_eps\n", + "qubitization_qpe_hubbard_model_small = QubitizationQPE(\n", + " walk, LPResourceState.from_standard_deviation_eps(qpe_eps)\n", ")" ] }, @@ -136,7 +137,7 @@ "\n", "from qualtran.bloqs.chemistry.sparse.prepare_test import build_random_test_integrals\n", "from qualtran.bloqs.chemistry.sparse.walk_operator import get_walk_operator_for_sparse_chem_ham\n", - "from qualtran.bloqs.phase_estimation import QubitizationQPE\n", + "from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE\n", "\n", "num_spatial = 6\n", "tpq, eris = build_random_test_integrals(num_spatial // 2, seed=7)\n", @@ -147,7 +148,9 @@ "algo_eps = 0.0016\n", "qlambda = np.sum(np.abs(tpq)) + 0.5 * np.sum(np.abs(eris))\n", "qpe_eps = algo_eps / (qlambda * np.sqrt(2))\n", - "qubitization_qpe_sparse_chem = QubitizationQPE.from_standard_deviation_eps(walk, qpe_eps)" + "qubitization_qpe_sparse_chem = QubitizationQPE(\n", + " walk, LPResourceState.from_standard_deviation_eps(qpe_eps)\n", + ")" ] }, { @@ -163,6 +166,7 @@ "\n", "from qualtran.bloqs.chemistry.thc.prepare_test import build_random_test_integrals\n", "from qualtran.bloqs.chemistry.thc.walk_operator import get_walk_operator_for_thc_ham\n", + "from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE\n", "\n", "# Li et al parameters from openfermion.resource_estimates.thc.compute_cost_thc_test\n", "num_spinorb = 152\n", @@ -184,7 +188,9 @@ "\n", "algo_eps = 0.0016\n", "qpe_eps = algo_eps / (walk.block_encoding.alpha * 2**0.5)\n", - "qubitization_qpe_chem_thc = QubitizationQPE.from_standard_deviation_eps(walk, qpe_eps)" + "qubitization_qpe_chem_thc = QubitizationQPE(\n", + " walk, LPResourceState.from_standard_deviation_eps(qpe_eps)\n", + ")" ] }, { diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe.py b/qualtran/bloqs/phase_estimation/qubitization_qpe.py index 60b8a2f75..dba21d94c 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe.py +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe.py @@ -18,11 +18,11 @@ import cirq import numpy as np -from qualtran import Bloq, bloq_example, BloqDocSpec, GateWithRegisters, QFxp, Register, Signature -from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState +from qualtran import Bloq, bloq_example, BloqDocSpec, GateWithRegisters, Register, Signature +from qualtran.bloqs.phase_estimation.qpe_window_state import QPEWindowStateBase from qualtran.bloqs.qft.qft_text_book import QFTTextBook from qualtran.bloqs.qubitization.qubitization_walk_operator import QubitizationWalkOperator -from qualtran.symbolics import ceil, is_symbolic, log2, pi, SymbolicFloat, SymbolicInt +from qualtran.symbolics import is_symbolic, SymbolicInt if TYPE_CHECKING: from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -36,7 +36,7 @@ class QubitizationQPE(GateWithRegisters): for learning eigenphases of the `walk` operator with `m` bits of accuracy. The circuit is implemented as given in Fig.2 of Ref-1. - ``` + ``` ┌─────────┐ ┌─────────┐ |0> -│ │-------------------------(0)---(0)---│ │---M--- [m1]:highest bit │ │ | | │ │ @@ -47,7 +47,8 @@ class QubitizationQPE(GateWithRegisters): |0> -│ │---@----+-----+--+-----+--+-----+----│ │---M--- [m4]:lowest bit └─────────┘ | | | | | | | └─────────┘ |Psi> ---------------W----R-W^2-R--R-W^4-R--R-W^8-R---------------------- |Psi> - ``` + + ``` TODO: Note that there are slight differences between the Fig2 of the Ref[1] and the circuit implemented here. Further investigation is required to reconcile the difference. @@ -72,42 +73,19 @@ class QubitizationQPE(GateWithRegisters): """ walk: QubitizationWalkOperator - m_bits: SymbolicInt - ctrl_state_prep: Bloq = attrs.field() + ctrl_state_prep: QPEWindowStateBase qft_inv: Bloq = attrs.field() - @ctrl_state_prep.default - def _default_state_prep(self): - return LPResourceState(self.m_bits) - @qft_inv.default def _default_inverse_qft(self): return QFTTextBook(self.m_bits, with_reverse=True).adjoint() def __attrs_post_init__(self): - assert is_symbolic(self.m_bits) or ( - self.ctrl_state_prep.signature.n_qubits() == self.m_bits - ) - - @classmethod - def from_standard_deviation_eps(cls, walk: QubitizationWalkOperator, eps: SymbolicFloat): - r"""Estimate the phase $\phi$ with uncertainty in standard deviation bounded by $\epsilon$. - - The standard deviation of phase estimation using optimal resource states scales as the - square of Holevo variance $\tan{\frac{\pi}{2^m}}$. - This bound can be used to estimate the size of the phase register s.t. the estimated phase - has a standard deviation of at-most $\epsilon$. See the class docstring for more details. - - ``` - m = ceil(log2(pi/eps)) - ``` - - Args: - walk: Walk operator to obtain phase estimate of. - eps: Maximum standard deviation of the estimated phase. - """ - m_bits = ceil(log2(pi(eps) / eps)) - return QubitizationQPE(walk=walk, m_bits=m_bits) + assert is_symbolic(self.m_bits) or (self.qft_inv.signature.n_qubits() == self.m_bits) + + @cached_property + def m_bits(self) -> SymbolicInt: + return self.ctrl_state_prep.m_bits @cached_property def target_registers(self) -> Tuple[Register, ...]: @@ -115,7 +93,7 @@ def target_registers(self) -> Tuple[Register, ...]: @cached_property def phase_registers(self) -> Tuple[Register, ...]: - return (Register('qpe_reg', QFxp(self.m_bits, self.m_bits)),) + return tuple(self.ctrl_state_prep.signature) @cached_property def signature(self) -> Signature: @@ -164,7 +142,7 @@ def _qubitization_qpe_hubbard_model_small() -> QubitizationQPE: from qualtran.bloqs.chemistry.hubbard_model.qubitization import ( get_walk_operator_for_hubbard_model, ) - from qualtran.bloqs.phase_estimation import QubitizationQPE + from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE x_dim, y_dim, t = 2, 2, 2 u = 4 * t @@ -174,8 +152,8 @@ def _qubitization_qpe_hubbard_model_small() -> QubitizationQPE: N = x_dim * y_dim * 2 qlambda = 2 * N * t + (N * u) // 2 qpe_eps = algo_eps / (qlambda * np.sqrt(2)) - qubitization_qpe_hubbard_model_small = QubitizationQPE.from_standard_deviation_eps( - walk, qpe_eps + qubitization_qpe_hubbard_model_small = QubitizationQPE( + walk, LPResourceState.from_standard_deviation_eps(qpe_eps) ) return qubitization_qpe_hubbard_model_small @@ -187,7 +165,7 @@ def _qubitization_qpe_hubbard_model_large() -> QubitizationQPE: from qualtran.bloqs.chemistry.hubbard_model.qubitization import ( get_walk_operator_for_hubbard_model, ) - from qualtran.bloqs.phase_estimation import QubitizationQPE + from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE x_dim, y_dim, t = 20, 20, 20 u = 4 * t @@ -197,8 +175,8 @@ def _qubitization_qpe_hubbard_model_large() -> QubitizationQPE: N = x_dim * y_dim * 2 qlambda = 2 * N * t + (N * u) // 2 qpe_eps = algo_eps / (qlambda * np.sqrt(2)) - qubitization_qpe_hubbard_model_large = QubitizationQPE.from_standard_deviation_eps( - walk, qpe_eps + qubitization_qpe_hubbard_model_large = QubitizationQPE( + walk, LPResourceState.from_standard_deviation_eps(qpe_eps) ) return qubitization_qpe_hubbard_model_large @@ -209,6 +187,7 @@ def _qubitization_qpe_chem_thc() -> QubitizationQPE: from qualtran.bloqs.chemistry.thc.prepare_test import build_random_test_integrals from qualtran.bloqs.chemistry.thc.walk_operator import get_walk_operator_for_thc_ham + from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE # Li et al parameters from openfermion.resource_estimates.thc.compute_cost_thc_test num_spinorb = 152 @@ -230,7 +209,9 @@ def _qubitization_qpe_chem_thc() -> QubitizationQPE: algo_eps = 0.0016 qpe_eps = algo_eps / (walk.block_encoding.alpha * 2**0.5) - qubitization_qpe_chem_thc = QubitizationQPE.from_standard_deviation_eps(walk, qpe_eps) + qubitization_qpe_chem_thc = QubitizationQPE( + walk, LPResourceState.from_standard_deviation_eps(qpe_eps) + ) return qubitization_qpe_chem_thc @@ -240,7 +221,7 @@ def _qubitization_qpe_sparse_chem() -> QubitizationQPE: from qualtran.bloqs.chemistry.sparse.prepare_test import build_random_test_integrals from qualtran.bloqs.chemistry.sparse.walk_operator import get_walk_operator_for_sparse_chem_ham - from qualtran.bloqs.phase_estimation import QubitizationQPE + from qualtran.bloqs.phase_estimation import LPResourceState, QubitizationQPE num_spatial = 6 tpq, eris = build_random_test_integrals(num_spatial // 2, seed=7) @@ -251,7 +232,9 @@ def _qubitization_qpe_sparse_chem() -> QubitizationQPE: algo_eps = 0.0016 qlambda = np.sum(np.abs(tpq)) + 0.5 * np.sum(np.abs(eris)) qpe_eps = algo_eps / (qlambda * np.sqrt(2)) - qubitization_qpe_sparse_chem = QubitizationQPE.from_standard_deviation_eps(walk, qpe_eps) + qubitization_qpe_sparse_chem = QubitizationQPE( + walk, LPResourceState.from_standard_deviation_eps(qpe_eps) + ) return qubitization_qpe_sparse_chem diff --git a/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py b/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py index 79d7c266a..2888ef7da 100644 --- a/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py +++ b/qualtran/bloqs/phase_estimation/qubitization_qpe_test.py @@ -15,9 +15,9 @@ import numpy as np import pytest -from qualtran.bloqs.basic_gates import Hadamard, OnEach from qualtran.bloqs.for_testing.qubitization_walk_test import get_uniform_pauli_qubitized_walk from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState +from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState from qualtran.bloqs.phase_estimation.qubitization_qpe import ( _qubitization_qpe_chem_thc, _qubitization_qpe_hubbard_model_small, @@ -62,8 +62,10 @@ def test_qubitization_phase_estimation_of_walk(num_terms: int, use_resource_stat # 1. Construct QPE bloq - state_prep = LPResourceState(precision) if use_resource_state else OnEach(precision, Hadamard()) - gh = GateHelper(QubitizationQPE(walk, precision, ctrl_state_prep=state_prep)) + state_prep = ( + LPResourceState(precision) if use_resource_state else RectangularWindowState(precision) + ) + gh = GateHelper(QubitizationQPE(walk, ctrl_state_prep=state_prep)) qpe_reg, selection, target = (gh.quregs['qpe_reg'], gh.quregs['selection'], gh.quregs['target']) for eig_idx, eig_val in enumerate(eigen_values): # Apply QPE to determine eigenvalue for walk operator W on initial state |L>|k> diff --git a/qualtran/bloqs/phase_estimation/text_book_qpe.ipynb b/qualtran/bloqs/phase_estimation/text_book_qpe.ipynb index 592b7d303..139af0f36 100644 --- a/qualtran/bloqs/phase_estimation/text_book_qpe.ipynb +++ b/qualtran/bloqs/phase_estimation/text_book_qpe.ipynb @@ -104,8 +104,8 @@ "\n", "Here $\\varphi$ is the true phase and $\\tilde{\\varphi}$ is the estimated phase.\n", "\n", - "`TextbookQPE.from_precision_and_delta` method can be used to instantiate the Bloq with\n", - "parameters $m$ and $\\delta$ as described above.\n", + "`TextbookQPE(unitary, RectangularWindow.from_precision_and_delta(precision, delta))` method\n", + "can be used to instantiate the Bloq with parameters $m$ and $\\delta$ as described above.\n", "\n", "### Dependence of $m$ using standard deviation $\\epsilon$ as the measure of uncertainty\n", "A stronger way to bound the uncertainty in the obtained phase is to bound the variance of the\n", @@ -125,14 +125,13 @@ " m = \\left\\lceil2\\log_2 \\left(\\frac{\\pi}{\\epsilon}\\right)\\right\\rceil\n", "$$\n", "\n", - "`TextbookQPE.from_standard_deviation_eps` method can be used to instantiate the Bloq with\n", - "parameter $\\epsilon$ as described above.\n", + "`TextbookQPE(unitary, RectangularWindow.from_standard_deviation_eps(eps))` method can be\n", + "used to instantiate the Bloq with parameter $\\epsilon$ as described above.\n", "\n", "\n", "#### Parameters\n", " - `unitary`: Bloq representing the unitary to run the phase estimation protocol on.\n", - " - `m_bits`: Bitsize of the phase register to be used during phase estimation.\n", - " - `ctrl_state_prep`: Bloq to prepare the control state on the phase register. Defaults to `OnEach(self.m_bits, Hadamard())`.\n", + " - `ctrl_state_prep`: Bloq to prepare the control state on the phase register.\n", " - `qft_inv`: Bloq to apply inverse QFT on the phase register. Defaults to `QFTTextBook(self.m_bits).adjoint()` \n", "\n", "#### Registers\n", @@ -176,9 +175,9 @@ "outputs": [], "source": [ "from qualtran.bloqs.basic_gates import ZPowGate\n", - "from qualtran.bloqs.phase_estimation import TextbookQPE\n", + "from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE\n", "\n", - "textbook_qpe_small = TextbookQPE(ZPowGate(exponent=2 * 0.234), 3)" + "textbook_qpe_small = TextbookQPE(ZPowGate(exponent=2 * 0.234), RectangularWindowState(3))" ] }, { @@ -193,11 +192,13 @@ "import sympy\n", "\n", "from qualtran.bloqs.basic_gates import ZPowGate\n", - "from qualtran.bloqs.phase_estimation import TextbookQPE\n", + "from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE\n", "\n", "theta = sympy.Symbol('theta')\n", "m_bits = sympy.Symbol('m')\n", - "textbook_qpe_using_m_bits = TextbookQPE(ZPowGate(exponent=2 * theta), m_bits)" + "textbook_qpe_using_m_bits = TextbookQPE(\n", + " ZPowGate(exponent=2 * theta), RectangularWindowState(m_bits)\n", + ")" ] }, { @@ -212,12 +213,12 @@ "import sympy\n", "\n", "from qualtran.bloqs.basic_gates import ZPowGate\n", - "from qualtran.bloqs.phase_estimation import TextbookQPE\n", + "from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE\n", "\n", "theta = sympy.Symbol('theta')\n", "epsilon = sympy.symbols('epsilon')\n", - "textbook_qpe_from_standard_deviation_eps = TextbookQPE.from_standard_deviation_eps(\n", - " ZPowGate(exponent=2 * theta), epsilon\n", + "textbook_qpe_from_standard_deviation_eps = TextbookQPE(\n", + " ZPowGate(exponent=2 * theta), RectangularWindowState.from_standard_deviation_eps(epsilon)\n", ")" ] }, @@ -233,12 +234,13 @@ "import sympy\n", "\n", "from qualtran.bloqs.basic_gates import ZPowGate\n", - "from qualtran.bloqs.phase_estimation import TextbookQPE\n", + "from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE\n", "\n", "theta = sympy.Symbol('theta')\n", "precision, delta = sympy.symbols('n, delta')\n", - "textbook_qpe_from_precision_and_delta = TextbookQPE.from_precision_and_delta(\n", - " ZPowGate(exponent=2 * theta), precision, delta\n", + "textbook_qpe_from_precision_and_delta = TextbookQPE(\n", + " ZPowGate(exponent=2 * theta),\n", + " RectangularWindowState.from_precision_and_delta(precision, delta),\n", ")" ] }, @@ -345,6 +347,134 @@ " \\sigma[\\tilde{\\phi}] = 2\\pi\\sigma[\\tilde{\\varphi}] = \\frac{\\pi}{\\sqrt{M}}\n", "$$" ] + }, + { + "cell_type": "markdown", + "id": "1733fdc0", + "metadata": { + "cq.autogen": "RectangularWindowState.bloq_doc.md" + }, + "source": [ + "## `RectangularWindowState`\n", + "Window state used in Textbook version of QPE. Applies Hadamard on all qubits.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: Size of the control register to prepare window state on. \n", + "\n", + "#### Registers\n", + " - `qpe_reg`: A `bitsize` sized RIGHT register.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8375feb8", + "metadata": { + "cq.autogen": "RectangularWindowState.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState" + ] + }, + { + "cell_type": "markdown", + "id": "61c031c5", + "metadata": { + "cq.autogen": "RectangularWindowState.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2e67e09", + "metadata": { + "cq.autogen": "RectangularWindowState.rectangular_window_state_small" + }, + "outputs": [], + "source": [ + "rectangular_window_state_small = RectangularWindowState(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d10555a9", + "metadata": { + "cq.autogen": "RectangularWindowState.rectangular_window_state_symbolic_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "rectangular_window_state_symbolic = RectangularWindowState(sympy.Symbol('n'))" + ] + }, + { + "cell_type": "markdown", + "id": "a9704923", + "metadata": { + "cq.autogen": "RectangularWindowState.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f7f46cf", + "metadata": { + "cq.autogen": "RectangularWindowState.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([rectangular_window_state_small, rectangular_window_state_symbolic],\n", + " ['`rectangular_window_state_small`', '`rectangular_window_state_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "f5a38462", + "metadata": { + "cq.autogen": "RectangularWindowState.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d153d32", + "metadata": { + "cq.autogen": "RectangularWindowState.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "rectangular_window_state_small_g, rectangular_window_state_small_sigma = rectangular_window_state_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(rectangular_window_state_small_g)\n", + "show_counts_sigma(rectangular_window_state_small_sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6c78a90", + "metadata": { + "cq.autogen": "RectangularWindowState.rectangular_window_state_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "rectangular_window_state_symbolic = RectangularWindowState(sympy.Symbol('n'))" + ] } ], "metadata": { diff --git a/qualtran/bloqs/phase_estimation/text_book_qpe.py b/qualtran/bloqs/phase_estimation/text_book_qpe.py index c55efac6d..6ab432f79 100644 --- a/qualtran/bloqs/phase_estimation/text_book_qpe.py +++ b/qualtran/bloqs/phase_estimation/text_book_qpe.py @@ -17,10 +17,10 @@ import attrs import cirq -from qualtran import Bloq, bloq_example, BloqDocSpec, GateWithRegisters, QFxp, Register, Signature -from qualtran.bloqs.basic_gates import Hadamard, OnEach +from qualtran import Bloq, bloq_example, BloqDocSpec, GateWithRegisters, Register, Signature +from qualtran.bloqs.phase_estimation.qpe_window_state import QPEWindowStateBase from qualtran.bloqs.qft.qft_text_book import QFTTextBook -from qualtran.symbolics import ceil, is_symbolic, log2, pi, SymbolicFloat, SymbolicInt +from qualtran.symbolics import is_symbolic, SymbolicInt if TYPE_CHECKING: from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -96,8 +96,8 @@ class TextbookQPE(GateWithRegisters): Here $\varphi$ is the true phase and $\tilde{\varphi}$ is the estimated phase. - `TextbookQPE.from_precision_and_delta` method can be used to instantiate the Bloq with - parameters $m$ and $\delta$ as described above. + `TextbookQPE(unitary, RectangularWindow.from_precision_and_delta(precision, delta))` method + can be used to instantiate the Bloq with parameters $m$ and $\delta$ as described above. ### Dependence of $m$ using standard deviation $\epsilon$ as the measure of uncertainty A stronger way to bound the uncertainty in the obtained phase is to bound the variance of the @@ -117,15 +117,13 @@ class TextbookQPE(GateWithRegisters): m = \left\lceil2\log_2 \left(\frac{\pi}{\epsilon}\right)\right\rceil $$ - `TextbookQPE.from_standard_deviation_eps` method can be used to instantiate the Bloq with - parameter $\epsilon$ as described above. + `TextbookQPE(unitary, RectangularWindow.from_standard_deviation_eps(eps))` method can be + used to instantiate the Bloq with parameter $\epsilon$ as described above. Args: unitary: Bloq representing the unitary to run the phase estimation protocol on. - m_bits: Bitsize of the phase register to be used during phase estimation. - ctrl_state_prep: Bloq to prepare the control state on the phase register. Defaults to - `OnEach(self.m_bits, Hadamard())`. + ctrl_state_prep: Bloq to prepare the control state on the phase register. qft_inv: Bloq to apply inverse QFT on the phase register. Defaults to `QFTTextBook(self.m_bits).adjoint()` @@ -144,61 +142,19 @@ class TextbookQPE(GateWithRegisters): """ unitary: Bloq - m_bits: SymbolicInt - ctrl_state_prep: Bloq = attrs.field() + ctrl_state_prep: QPEWindowStateBase qft_inv: Bloq = attrs.field() - @ctrl_state_prep.default - def _default_state_prep(self): - return OnEach(self.m_bits, Hadamard()) - @qft_inv.default def _default_inverse_qft(self): return QFTTextBook(self.m_bits, with_reverse=True).adjoint() def __attrs_post_init__(self): - assert is_symbolic(self.m_bits) or ( - self.ctrl_state_prep.signature.n_qubits() == self.m_bits - ) - - @classmethod - def from_precision_and_delta(cls, unitary: Bloq, precision: SymbolicInt, delta: SymbolicFloat): - r"""Estimate $\varphi$ to $precision$ bits with $1-\delta$ success probability. - - Uses Eq 5.35 from Neilson and Chuang to estimate the size of phase register s.t. we can - estimate the phase $\varphi$ to $precision$ bits of accuracy with probability at least - $1 - \delta$. See the class docstring for more details. - - ``` - m = n + ceil(log2(2 + 1/(2*delta))) - ``` - - Args: - unitary: Unitary operation to obtain phase estimate of. - precision: Number of bits of precision - delta: Probability of success. - """ - m_bits = precision + ceil(log2(2 + 1 / (2 * delta))) - return TextbookQPE(unitary=unitary, m_bits=m_bits) - - @classmethod - def from_standard_deviation_eps(cls, unitary: Bloq, eps: SymbolicFloat): - r"""Estimate the phase $\phi$ with uncertainty in standard deviation bounded by $\epsilon$. - - The standard deviation of textbook phase estimation scales as $\frac{2\pi}{\sqrt{2^{m}}}$. - This bound can be used to estimate the size of the phase register s.t. the estimated phase - has a standard deviation of at-most $\epsilon$. See the class docstring for more details. - - ``` - m = ceil(2*log2(pi/eps)) - ``` - - Args: - unitary: Unitary operation to obtain phase estimate of. - eps: Maximum standard deviation of the estimated phase. - """ - m_bits = ceil(2 * log2(pi(eps) / eps)) - return TextbookQPE(unitary=unitary, m_bits=m_bits) + assert is_symbolic(self.m_bits) or (self.qft_inv.signature.n_qubits() == self.m_bits) + + @cached_property + def m_bits(self) -> SymbolicInt: + return self.ctrl_state_prep.m_bits @cached_property def target_registers(self) -> Tuple[Register, ...]: @@ -206,7 +162,7 @@ def target_registers(self) -> Tuple[Register, ...]: @cached_property def phase_registers(self) -> Tuple[Register, ...]: - return (Register('qpe_reg', QFxp(self.m_bits, self.m_bits)),) + return tuple(self.ctrl_state_prep.signature) @cached_property def signature(self) -> Signature: @@ -217,7 +173,6 @@ def decompose_from_registers( ) -> Iterator[cirq.OP_TREE]: target_quregs = {reg.name: quregs[reg.name] for reg in self.target_registers} unitary_op = self.unitary.on_registers(**target_quregs) - phase_qubits = quregs['qpe_reg'] yield self.ctrl_state_prep.on(*phase_qubits) @@ -227,11 +182,9 @@ def decompose_from_registers( def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: # Assumes self.unitary is not fast forwardable. - from qualtran import Controlled, CtrlSpec - return { (self.ctrl_state_prep, 1), - (Controlled(self.unitary, CtrlSpec()), (2**self.m_bits) - 1), + (self.unitary.controlled(), (2**self.m_bits) - 1), (self.qft_inv, 1), } @@ -239,9 +192,9 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: @bloq_example def _textbook_qpe_small() -> TextbookQPE: from qualtran.bloqs.basic_gates import ZPowGate - from qualtran.bloqs.phase_estimation import TextbookQPE + from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE - textbook_qpe_small = TextbookQPE(ZPowGate(exponent=2 * 0.234), 3) + textbook_qpe_small = TextbookQPE(ZPowGate(exponent=2 * 0.234), RectangularWindowState(3)) return textbook_qpe_small @@ -250,11 +203,13 @@ def _textbook_qpe_using_m_bits() -> TextbookQPE: import sympy from qualtran.bloqs.basic_gates import ZPowGate - from qualtran.bloqs.phase_estimation import TextbookQPE + from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE theta = sympy.Symbol('theta') m_bits = sympy.Symbol('m') - textbook_qpe_using_m_bits = TextbookQPE(ZPowGate(exponent=2 * theta), m_bits) + textbook_qpe_using_m_bits = TextbookQPE( + ZPowGate(exponent=2 * theta), RectangularWindowState(m_bits) + ) return textbook_qpe_using_m_bits @@ -263,12 +218,13 @@ def _textbook_qpe_from_precision_and_delta() -> TextbookQPE: import sympy from qualtran.bloqs.basic_gates import ZPowGate - from qualtran.bloqs.phase_estimation import TextbookQPE + from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE theta = sympy.Symbol('theta') precision, delta = sympy.symbols('n, delta') - textbook_qpe_from_precision_and_delta = TextbookQPE.from_precision_and_delta( - ZPowGate(exponent=2 * theta), precision, delta + textbook_qpe_from_precision_and_delta = TextbookQPE( + ZPowGate(exponent=2 * theta), + RectangularWindowState.from_precision_and_delta(precision, delta), ) return textbook_qpe_from_precision_and_delta @@ -278,12 +234,12 @@ def _textbook_qpe_from_standard_deviation_eps() -> TextbookQPE: import sympy from qualtran.bloqs.basic_gates import ZPowGate - from qualtran.bloqs.phase_estimation import TextbookQPE + from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE theta = sympy.Symbol('theta') epsilon = sympy.symbols('epsilon') - textbook_qpe_from_standard_deviation_eps = TextbookQPE.from_standard_deviation_eps( - ZPowGate(exponent=2 * theta), epsilon + textbook_qpe_from_standard_deviation_eps = TextbookQPE( + ZPowGate(exponent=2 * theta), RectangularWindowState.from_standard_deviation_eps(epsilon) ) return textbook_qpe_from_standard_deviation_eps diff --git a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py index c94f5fc06..9a7d4fb72 100644 --- a/qualtran/bloqs/phase_estimation/text_book_qpe_test.py +++ b/qualtran/bloqs/phase_estimation/text_book_qpe_test.py @@ -15,9 +15,10 @@ import numpy as np import pytest -from qualtran.bloqs.basic_gates import Hadamard, OnEach, ZPowGate +from qualtran.bloqs.basic_gates import ZPowGate from qualtran.bloqs.for_testing.qubitization_walk_test import get_uniform_pauli_qubitized_walk from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState +from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState from qualtran.bloqs.phase_estimation.text_book_qpe import TextbookQPE from qualtran.cirq_interop.testing import GateHelper @@ -36,7 +37,7 @@ def simulate_theta_estimate(circuit, measurement_register) -> float: @pytest.mark.parametrize('theta', [0.234, 0.78, 0.54]) def test_textbook_phase_estimation_zpow_theta(theta): precision, error_bound = 3, 0.1 - gh = GateHelper(TextbookQPE(ZPowGate(exponent=2 * theta), precision)) + gh = GateHelper(TextbookQPE(ZPowGate(exponent=2 * theta), RectangularWindowState(precision))) circuit = cirq.Circuit(cirq.X(*gh.quregs['q']), cirq.decompose_once(gh.operation)) precision_register = gh.quregs['qpe_reg'] assert abs(simulate_theta_estimate(circuit, precision_register) - theta) < error_bound @@ -57,8 +58,10 @@ def test_textbook_phase_estimation_qubitized_walk(num_terms: int, use_resource_s eigen_values, eigen_vectors = np.linalg.eigh(ham.matrix()) - state_prep = LPResourceState(precision) if use_resource_state else OnEach(precision, Hadamard()) - gh = GateHelper(TextbookQPE(walk, precision, ctrl_state_prep=state_prep)) + state_prep = ( + LPResourceState(precision) if use_resource_state else RectangularWindowState(precision) + ) + gh = GateHelper(TextbookQPE(walk, ctrl_state_prep=state_prep)) # 1. Construct QPE bloq qpe_reg, selection, target = (gh.quregs['qpe_reg'], gh.quregs['selection'], gh.quregs['target']) for eig_idx, eig_val in enumerate(eigen_values): diff --git a/qualtran/bloqs/reflections/reflection_using_prepare.py b/qualtran/bloqs/reflections/reflection_using_prepare.py index 0f3588990..3878edc74 100644 --- a/qualtran/bloqs/reflections/reflection_using_prepare.py +++ b/qualtran/bloqs/reflections/reflection_using_prepare.py @@ -28,7 +28,7 @@ from qualtran.bloqs.mcmt import MultiControlZ from qualtran.bloqs.reflections.prepare_identity import PrepareIdentity from qualtran.resource_counting.generalizers import ignore_split_join -from qualtran.symbolics.types import SymbolicInt +from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt if TYPE_CHECKING: from qualtran.bloqs.block_encoding.lcu_block_encoding import BlackBoxPrepare @@ -164,10 +164,11 @@ def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.Circ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: n_phase_control = sum(reg.total_bits() for reg in self.selection_registers) + cvs = HasLength(n_phase_control) if is_symbolic(n_phase_control) else [0] * n_phase_control costs: Set['BloqCountT'] = { (self.prepare_gate, 1), (self.prepare_gate.adjoint(), 1), - (MultiControlZ([0] * n_phase_control), 1), + (MultiControlZ(cvs), 1), } if self.control_val is None: costs.add((XGate(), 2)) diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 91a1303f8..d0b70d15c 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -124,6 +124,7 @@ import qualtran.bloqs.multiplexers.selected_majorana_fermion import qualtran.bloqs.multiplexers.unary_iteration_bloq import qualtran.bloqs.phase_estimation.lp_resource_state +import qualtran.bloqs.phase_estimation.qpe_window_state import qualtran.bloqs.phase_estimation.qubitization_qpe import qualtran.bloqs.phase_estimation.text_book_qpe import qualtran.bloqs.qft.approximate_qft @@ -233,6 +234,7 @@ "qualtran.bloqs.basic_gates.z_basis.ZGate": qualtran.bloqs.basic_gates.z_basis.ZGate, "qualtran.bloqs.basic_gates.z_basis.ZeroEffect": qualtran.bloqs.basic_gates.z_basis.ZeroEffect, "qualtran.bloqs.basic_gates.z_basis.ZeroState": qualtran.bloqs.basic_gates.z_basis.ZeroState, + "qualtran.bloqs.basic_gates.z_basis.CZ": qualtran.bloqs.basic_gates.z_basis.CZ, "qualtran.bloqs.basic_gates.power.Power": qualtran.bloqs.basic_gates.power.Power, "qualtran.bloqs.block_encoding.lcu_block_encoding.SelectBlockEncoding": qualtran.bloqs.block_encoding.lcu_block_encoding.SelectBlockEncoding, "qualtran.bloqs.block_encoding.lcu_block_encoding.LCUBlockEncoding": qualtran.bloqs.block_encoding.lcu_block_encoding.LCUBlockEncoding, @@ -366,6 +368,7 @@ "qualtran.bloqs.multiplexers.select_pauli_lcu.SelectPauliLCU": qualtran.bloqs.multiplexers.select_pauli_lcu.SelectPauliLCU, "qualtran.bloqs.multiplexers.selected_majorana_fermion.SelectedMajoranaFermion": qualtran.bloqs.multiplexers.selected_majorana_fermion.SelectedMajoranaFermion, "qualtran.bloqs.multiplexers.unary_iteration_bloq.UnaryIterationGate": qualtran.bloqs.multiplexers.unary_iteration_bloq.UnaryIterationGate, + "qualtran.bloqs.phase_estimation.qpe_window_state.RectangularWindowState": qualtran.bloqs.phase_estimation.qpe_window_state.RectangularWindowState, "qualtran.bloqs.phase_estimation.lp_resource_state.LPRSInterimPrep": qualtran.bloqs.phase_estimation.lp_resource_state.LPRSInterimPrep, "qualtran.bloqs.phase_estimation.lp_resource_state.LPResourceState": qualtran.bloqs.phase_estimation.lp_resource_state.LPResourceState, "qualtran.bloqs.phase_estimation.qubitization_qpe.QubitizationQPE": qualtran.bloqs.phase_estimation.qubitization_qpe.QubitizationQPE,