Source code for qarp.blocks._primitives.amplitude_amplification_block
"""Phase-exact amplitude-amplification iterate."""
from copy import deepcopy
from typing import List, Optional
import qarpx as qx
from .._block import AnyBlock, CompositeBlockBase
from .reflection_block import ReflectionBlock
def validate_amplification_blocks(state_preparation: AnyBlock, oracle: AnyBlock) -> int:
"""Validate the shared structural contract and return its register width.
Shared by :class:`AmplitudeAmplificationBlock` and the composite
amplitude-amplification algorithm, which must reject identical inputs.
"""
if not isinstance(state_preparation, qx.Block):
raise TypeError("state_preparation must be a Block instance")
if not isinstance(oracle, qx.Block):
raise TypeError("oracle must be a Block instance")
if state_preparation.n_qubits < 1:
raise ValueError("state_preparation must act on at least one qubit")
if oracle.n_qubits < 1:
raise ValueError("oracle must act on at least one qubit")
if state_preparation.n_qubits != oracle.n_qubits:
raise ValueError("state_preparation and oracle must act on the same number of qubits")
# The iterate reflects about |0…0> on the *whole* register, ancillas included;
# a postselected prep only holds its state on one ancilla branch.
if getattr(state_preparation, "ancilla_postselection", None) is not None:
raise ValueError(
"amplitude amplification needs a deterministic state preparation "
"(ancilla_postselection is None): the reflection about |0...0> would "
f"act on the ancillas of {type(state_preparation).__name__}"
)
return state_preparation.n_qubits
def validate_power(power: int) -> int:
"""Validate and return a non-negative amplification power (rejects ``bool``)."""
if isinstance(power, bool) or not isinstance(power, int):
raise TypeError("power must be an integer")
if power < 0:
raise ValueError("power must be non-negative")
return power
[docs]
class AmplitudeAmplificationBlock(CompositeBlockBase):
r"""The phase-exact amplitude-amplification iterate.
For a state-preparation unitary ``A`` and a good-state phase oracle
``O_good = I - 2 Pi_good``, this block implements exactly
``Q = A R0 A_dagger O_good``,
where :class:`ReflectionBlock` supplies
``R0 = 2|0...0><0...0| - I``. Consequently, the circuit-time child order
is ``O_good``, ``A_dagger``, ``R0``, ``A``.
The oracle contract is mathematical: ``oracle`` must be a unitary block
with the stated phase convention. The constructor checks its type and
width but deliberately does not build a dense matrix to prove its
semantics — that proof is exponential in the register width, so the
phase convention is the caller's promise. In particular, a raw
``ReflectionBlock`` about the good subspace implements
``2 Pi_good - I = -(I - 2 Pi_good)`` — the exact *negative* of a
good-state oracle. To use one as an oracle, compose it with a
``gphase(pi)`` block to restore the sign. Getting this wrong is
invisible in standalone sampling (probabilities are phase-blind) but
shifts every controlled eigenphase by one half, which silently corrupts
amplitude estimation built on the controlled iterate.
Caller-owned inputs are deep-copied at construction. Building this block
therefore does not build, retarget, or otherwise mutate either input.
This convention is Eq. (1) of Brassard, Hoyer, Mosca, and Tapp,
*Quantum Amplitude Amplification and Estimation*,
arXiv:quant-ph/0005055. Their zero-state reflection is
``S0 = I - 2|0><0|`` and their iterate is ``-A S0 A^-1 S_chi``.
OpenQARP's ``ReflectionBlock`` is ``R0 = -S0``, yielding the exact form above.
``power`` repeats the iterate: the block implements ``Q^power``. The default
``power=1`` is the single iterate ``Q``; ``power=0`` is the identity (empty
circuit). This is the only knob a fixed-schedule amplitude-amplification
consumer needs — Grover applies ``Q^k`` after a uniform preparation, and
maximum-likelihood amplitude estimation runs several powers ``Q^{m_k}``
(including ``m_0 = 0``) after ``A``.
Args:
state_preparation: Unitary ``A`` preparing the initial state from
``|0...0>``.
oracle: Unitary implementing exactly ``I - 2 Pi_good`` on the same
register as ``state_preparation``.
target_qubits: Optional placement of the complete iterate.
name: Block name.
power: Non-negative number of times to repeat the iterate ``Q`` (default
``1``; ``0`` is the identity). This option is keyword-only.
"""
def __init__(
self,
state_preparation: AnyBlock,
oracle: AnyBlock,
target_qubits: Optional[List[int]] = None,
name: str = "AmplitudeAmplification",
*,
power: int = 1,
) -> None:
n_qubits = validate_amplification_blocks(state_preparation, oracle)
self.power = validate_power(power)
self.state_preparation = deepcopy(state_preparation)
self.oracle = deepcopy(oracle)
super().__init__(
n_qubits=n_qubits,
target_qubits=target_qubits,
name=name,
)
[docs]
def build_vanilla(self) -> None:
local_qubits = list(range(self.n_qubits))
# power copies of Q = A R0 A_dagger O_good; power=0 emits nothing (identity).
for _ in range(self.power):
# Each child is an independent copy: materialising a pending dagger or
# symbol operation on one child cannot alter another occurrence of A.
oracle = deepcopy(self.oracle)
state_dagger = deepcopy(self.state_preparation).dagger()
reflection = ReflectionBlock(self.n_qubits)
state_preparation = deepcopy(self.state_preparation)
for child in (oracle, state_dagger, reflection, state_preparation):
# Input placement belongs to the caller's surrounding circuit;
# inside Q, A and O_good both act on the complete local register.
child.target_qubits = local_qubits
self.add_wired_child(child)