Source code for qarp.algorithms._composite.grover
"""Grover search with a caller-declared marked-state count."""
from collections.abc import Sequence
from copy import deepcopy
from typing import Optional, Self, cast
from ..._types import SamplingDictionary
from ...blocks import AnyBlock
from ...blocks._primitives import GroverBlock
from ...blocks._primitives.grover_block import (
optimal_grover_iterations,
validate_grover_inputs,
)
from ...endianness import bits_to_label
from ...engines import Engine
from .._primitives import PrimitiveAlgorithm, Sampler
from .composite_algorithm import CompositeAlgorithm
[docs]
class Grover(CompositeAlgorithm):
"""Search a uniform basis-state space with a known marked-state count.
``good_states`` is optional reporting metadata. It is never used to build
or modify the oracle; when supplied, its returned probability mass is
exposed as ``success_probability``. It is **not** cross-checked against the
oracle (an opaque unitary cannot be inspected without an exponential dense
matrix), so declaring labels the oracle does not actually mark yields a
misleading ``success_probability`` — the labels are the caller's promise.
``most_likely_states`` is only meaningful once amplification has
concentrated the distribution. When the optimal iteration count is zero
(``n_marked >= N/2``, so the uniform state is already at or past the
amplification optimum) the distribution stays (near-)uniform and
``most_likely_states`` is the entire register; read
``predicted_success_probability`` to see that no concentration occurred.
The algorithm follows Grover, arXiv:quant-ph/9605043, with the known-count
analysis of Boyer, Brassard, Hoyer, and Tapp, arXiv:quant-ph/9605034.
Args:
oracle: Phase oracle implementing exactly ``I - 2 Pi_good``.
n_marked: Number of basis states marked by the oracle.
good_states: Optional explicit marked integer labels for reporting only.
primitive: Sampling primitive. Defaults to a private ``Sampler``.
engine: Execution engine.
"""
def __init__(
self,
oracle: AnyBlock,
n_marked: int = 1,
*,
good_states: Optional[Sequence[int]] = None,
primitive: Optional[PrimitiveAlgorithm] = None,
engine: Optional[Engine] = None,
) -> None:
self.n_qubits, search_size = validate_grover_inputs(oracle, n_marked)
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 >= search_size:
raise ValueError(f"good-state label {label} is outside [0, {search_size})")
if len(labels) != len(set(labels)):
raise ValueError("good_states must contain unique labels")
if len(labels) != n_marked:
raise ValueError("good_states must contain exactly n_marked labels")
validated_good_states = labels
if primitive is None:
primitive = Sampler()
elif not isinstance(primitive, Sampler):
raise TypeError("Grover requires a sampling primitive")
if primitive.initial_state is not None:
raise ValueError("Grover requires the sampler initial_state to be None")
super().__init__(primitive=primitive, engine=engine)
self.oracle = deepcopy(oracle)
self.n_marked = n_marked
self.good_states = validated_good_states
self.n_iterations, self.predicted_success_probability = optimal_grover_iterations(
self.n_qubits, self.n_marked
)
self.block: Optional[AnyBlock] = None
self.distribution: Optional[SamplingDictionary] = None
self.most_likely_states: Optional[tuple[int, ...]] = None
self.success_probability: Optional[float] = None
[docs]
def build(self) -> Self:
"""Build and compile uniform preparation plus amplification."""
self.block = GroverBlock(self.oracle, self.n_marked).build()
sampler = cast(Sampler, self.primitive)
sampler.ket = self.block
sampler.measured_qubits = list(range(self.n_qubits))
self.engine.build([self.primitive])
self.distribution = None
self.most_likely_states = None
self.success_probability = None
return self
[docs]
def run(self) -> SamplingDictionary:
"""Execute once and return the full LSB-first search 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("Grover requires a sampling primitive")
self.distribution = cast(SamplingDictionary, result)
maximum = max(self.distribution.values())
self.most_likely_states = tuple(
sorted(
bits_to_label(bits)
for bits, probability in self.distribution.items()
if abs(probability - maximum) <= 1e-12
)
)
if self.good_states is None:
self.success_probability = None
else:
labels = set(self.good_states)
self.success_probability = sum(
probability
for bits, probability in self.distribution.items()
if bits_to_label(bits) in labels
)
return self.distribution