Source code for qarp.algorithms._primitives.termwise_swap_test

"""TermwiseSWAPTest primitive.

Composition wrapper: runs one :class:`SWAPTest` per ``bra`` against a
single ``ket``, sums the (optionally coefficient-weighted) overlap-squared
estimates.  Each child SWAPTest contributes one entry to ``sub_blocks``.
"""

from typing import List, Optional, Union

import qarpx as qx

from ..._types import Shots
from ...blocks import AnyBlock
from .primitive_algorithm import PrimitiveAlgorithm
from .swap_test import SWAPTest
from .target import Target


[docs] class TermwiseSWAPTest(PrimitiveAlgorithm): gradient_kind = "expectation" # every circuit's statistic is bilinear in its state returns_probability = True # run() is |⟨bra|ket⟩|², not the amplitude supported_targets = frozenset({Target.OVERLAP}) def __init__( self, bra: Union[AnyBlock, List[AnyBlock]], ket: Optional[AnyBlock] = None, coefficients: Optional[List[float]] = None, n_shots: Optional[Union[int, Shots]] = None, ): """ Args: bra: Single Block or list of bra-state preparation blocks. ket: Single ket-state preparation block. coefficients: Optional per-bra weight (default: all 1.0). n_shots: Number of shots; ``None`` defers to the engine default. """ if bra is None or ket is None: raise ValueError("Both bra and ket must be provided") bra_list: List[AnyBlock] = [bra] if isinstance(bra, qx.Block) else list(bra) super().__init__(ket=ket, bra=None, operator=None, n_shots=n_shots, target=Target.OVERLAP) self.bra: List[AnyBlock] = bra_list self.ket = ket self.coefficients = coefficients self.sub_algorithms: List[SWAPTest] = [] self.n_qubits: Optional[int] = None self.result_sum: Optional[float] = None self.result_list: Optional[List[float]] = None def _validate_inputs(self) -> None: if not self.bra: raise ValueError("bra list cannot be empty") for i, b in enumerate(self.bra): if not isinstance(b, qx.Block): raise TypeError(f"bra[{i}] must be a Block instance") if not isinstance(self.ket, qx.Block): raise TypeError("ket must be a Block instance") if self.coefficients is not None and len(self.coefficients) != len(self.bra): raise ValueError("coefficients must match the length of bra list")
[docs] def build(self) -> "TermwiseSWAPTest": self._validate_inputs() self.n_qubits = self.ket.n_qubits if self.coefficients is None: self.coefficients = [1.0] * len(self.bra) self.sub_algorithms = [] self.sub_blocks = [] for i, bra in enumerate(self.bra): child = SWAPTest(bra=bra, ket=self.ket, n_shots=self.n_shots) child.build() self.sub_algorithms.append(child) # Each child SWAPTest produces exactly one sub_block. self.sub_blocks.extend(child.sub_blocks) return self
[docs] def run(self, results: list) -> float: if not self.sub_algorithms: raise ValueError("TermwiseSWAPTest must be built before running") if len(results) != len(self.sub_algorithms): raise ValueError(f"Expected {len(self.sub_algorithms)} results, got {len(results)}") if self.coefficients is None: self.coefficients = [1.0] * len(self.sub_algorithms) per_term = [child.run([results[i]]) for i, child in enumerate(self.sub_algorithms)] weighted = [r * c for r, c in zip(per_term, self.coefficients, strict=True)] self.result_list = weighted self.result_sum = sum(weighted) return self.result_sum
[docs] def get_swap_test(self, index: int) -> SWAPTest: if not self.sub_algorithms: raise ValueError("TermwiseSWAPTest must be built first") return self.sub_algorithms[index]
def __len__(self) -> int: return len(self.bra) @property def n_bra(self) -> int: return len(self.bra) @property def n_sub_algorithms(self) -> int: return len(self.sub_algorithms) @property def expectation_type(self) -> str: return "real" # SWAPTest estimates overlap squared (real-valued). def __repr__(self) -> str: return ( f"TermwiseSWAPTest(n_bra={self.n_bra}, " f"coefficients={self.coefficients}, n_shots={self.n_shots})" )