Source code for qarp.algorithms._composite.amplitude_estimation
"""Canonical quantum amplitude estimation."""
from copy import deepcopy
from math import pi, sin
from typing import Optional, Self, cast
from ..._types import SamplingDictionary
from ...blocks import AnyBlock
from ...blocks._primitives import AmplitudeEstimationBlock
from ...blocks._primitives.amplitude_amplification_block import (
validate_amplification_blocks,
)
from ...blocks._primitives.amplitude_estimation_block import validate_n_ancilla
from ...endianness import bits_to_label
from ...engines import Engine
from .._primitives import PrimitiveAlgorithm, Sampler
from .composite_algorithm import CompositeAlgorithm
[docs]
class AmplitudeEstimation(CompositeAlgorithm):
r"""Estimate the initial good-state probability with canonical QAE.
The supplied oracle must implement exactly ``I - 2 Pi_good``. With
``M = 2**n_ancilla``, a sampled QPE label ``y`` maps to
``sin(pi*y/M)**2``. The conjugate phase branches are folded together at
``z = min(y, M-y)`` before selecting the most probable estimate.
This is the canonical estimator of Brassard, Hoyer, Mosca, and Tapp,
arXiv:quant-ph/0005055.
Args:
state_preparation: Unitary preparing the state whose amplitude is
estimated.
oracle: Phase-exact good-state oracle on the same register.
n_ancilla: Positive number of estimation qubits.
primitive: Sampling primitive. Defaults to a private ``Sampler``.
engine: Execution engine.
"""
def __init__(
self,
state_preparation: AnyBlock,
oracle: AnyBlock,
n_ancilla: int,
*,
primitive: Optional[PrimitiveAlgorithm] = None,
engine: Optional[Engine] = None,
) -> None:
self.n_state_qubits = validate_amplification_blocks(state_preparation, oracle)
self.n_ancilla = validate_n_ancilla(n_ancilla)
if primitive is None:
primitive = Sampler()
elif not isinstance(primitive, Sampler):
raise TypeError("AmplitudeEstimation requires a sampling primitive")
if primitive.initial_state is not None:
raise ValueError("AmplitudeEstimation requires the sampler initial_state to be None")
super().__init__(primitive=primitive, engine=engine)
self.state_preparation = deepcopy(state_preparation)
self.oracle = deepcopy(oracle)
self.block: Optional[AnyBlock] = None
self.distribution: Optional[SamplingDictionary] = None
self.folded_distribution: Optional[dict[int, float]] = None
self.phase_bin: Optional[int] = None
self.phase: Optional[float] = None
self.estimate: Optional[float] = None
self.result_probability: Optional[float] = None
[docs]
def build(self) -> Self:
"""Build and compile the canonical QAE circuit."""
self.block = AmplitudeEstimationBlock(
self.state_preparation,
self.oracle,
self.n_ancilla,
).build()
sampler = cast(Sampler, self.primitive)
sampler.ket = self.block
sampler.measured_qubits = list(range(self.n_ancilla))
self.engine.build([self.primitive])
self.distribution = None
self.folded_distribution = None
self.phase_bin = None
self.phase = None
self.estimate = None
self.result_probability = None
return self
[docs]
def run(self) -> float:
"""Execute QAE once and return the modal folded-bin estimate."""
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("AmplitudeEstimation requires a sampling primitive")
self.distribution = cast(SamplingDictionary, result)
modulus = 2**self.n_ancilla
folded: dict[int, float] = {}
for bits, probability in self.distribution.items():
label = bits_to_label(bits)
folded_label = min(label, modulus - label)
folded[folded_label] = folded.get(folded_label, 0.0) + probability
self.folded_distribution = folded
self.phase_bin = min(folded, key=lambda label: (-folded[label], label))
self.phase = self.phase_bin / modulus
self.estimate = sin(pi * self.phase) ** 2
self.result_probability = folded[self.phase_bin]
return self.estimate