Source code for qarp.algorithms._composite.amplitude_amplification

"""Composite amplitude-amplification algorithm."""

from collections.abc import Sequence
from copy import deepcopy
from typing import Optional, Self, cast

from ..._types import SamplingDictionary
from ...blocks import AmplitudeAmplificationBlock, AnyBlock, CompositeBlock
from ...blocks._primitives.amplitude_amplification_block import (
    validate_amplification_blocks,
)
from ...endianness import bits_to_label
from ...engines import Engine
from .._primitives import PrimitiveAlgorithm, Sampler
from .composite_algorithm import CompositeAlgorithm


[docs] class AmplitudeAmplification(CompositeAlgorithm): r"""Prepare a state and apply a fixed number of amplification iterates. The built circuit is ``A`` followed by ``n_iterations`` applications of :class:`~qarp.blocks.AmplitudeAmplificationBlock`. The supplied ``oracle`` must implement exactly ``I - 2 Pi_good``; see the block documentation for the phase convention. ``good_states`` is optional classical reporting metadata. It contains full-register integer labels and is used only to sum the returned sampling probabilities. It neither defines nor modifies the quantum oracle. The iterate and the probability law ``sin((2 * n_iterations + 1) * theta)**2`` follow Brassard, Hoyer, Mosca, and Tapp, *Quantum Amplitude Amplification and Estimation*, arXiv:quant-ph/0005055, Eqs. (1), (5), and (8). Args: state_preparation: Unitary ``A`` preparing the initial state. oracle: Good-state phase oracle on the same positive-width register. n_iterations: Explicit non-negative number of amplification iterates. good_states: Optional unique full-register integer labels whose returned probabilities are summed into ``success_probability``. primitive: Sampling primitive. Defaults to a private ``Sampler``. A sampler carrying ``initial_state`` is rejected: the amplification law assumes the circuit starts in ``|0...0>``, so ``A`` must act on that state and not on a seeded one. engine: Execution engine. Defaults to ``QarpEngine`` through the composite-algorithm base class. """ def __init__( self, state_preparation: AnyBlock, oracle: AnyBlock, n_iterations: int, *, good_states: Optional[Sequence[int]] = None, primitive: Optional[PrimitiveAlgorithm] = None, engine: Optional[Engine] = None, ) -> None: n_qubits = validate_amplification_blocks(state_preparation, oracle) if isinstance(n_iterations, bool) or not isinstance(n_iterations, int): raise TypeError("n_iterations must be an integer") if n_iterations < 0: raise ValueError("n_iterations must be non-negative") validated_good_states: Optional[tuple[int, ...]] if good_states is None: validated_good_states = None else: if isinstance(good_states, (str, bytes)) or not isinstance(good_states, Sequence): raise TypeError("good_states must be a sequence of integer labels") labels = tuple(good_states) for label in labels: if isinstance(label, bool) or not isinstance(label, int): raise TypeError("good_states must contain only integer labels") if label < 0 or label >= 2**n_qubits: raise ValueError(f"good-state label {label} is outside [0, {2**n_qubits})") if len(labels) != len(set(labels)): raise ValueError("good_states must contain unique labels") validated_good_states = labels if primitive is None: primitive = Sampler() elif not isinstance(primitive, Sampler): raise TypeError("AmplitudeAmplification requires a sampling primitive") if primitive.initial_state is not None: raise ValueError("AmplitudeAmplification requires the sampler initial_state to be None") super().__init__(primitive=primitive, engine=engine) # Keep private snapshots so neither circuit construction nor a later # caller mutation can change this algorithm's inputs. self.state_preparation = deepcopy(state_preparation) self.oracle = deepcopy(oracle) self.n_iterations = n_iterations self.n_qubits = n_qubits self.good_states = validated_good_states self.block: Optional[AnyBlock] = None self.distribution: Optional[SamplingDictionary] = None self.success_probability: Optional[float] = None
[docs] def build(self) -> Self: """Build and compile ``A`` followed by the requested iterates.""" state_preparation = deepcopy(self.state_preparation) state_preparation.target_qubits = list(range(self.n_qubits)) iterate = AmplitudeAmplificationBlock( self.state_preparation, self.oracle, power=self.n_iterations ) self.block = CompositeBlock( [state_preparation, iterate], n_qubits=self.n_qubits, name="AmplitudeAmplificationCircuit", ).build() self.primitive.ket = self.block self.engine.build([self.primitive]) self.distribution = None self.success_probability = None return self
[docs] def run(self) -> SamplingDictionary: """Execute once and return the LSB-first sampling distribution.""" if self.block is None or not self.block.is_built: raise ValueError("Circuit not built. Call build() before run().") result = self.engine.run()[0] if not isinstance(result, dict): raise TypeError("AmplitudeAmplification requires a sampling primitive") self.distribution = cast(SamplingDictionary, result) if self.good_states is None: self.success_probability = None else: good_labels = set(self.good_states) self.success_probability = sum( probability for bits, probability in self.distribution.items() if bits_to_label(bits) in good_labels ) return self.distribution