Source code for scikit_quri.state.overlap_estimator
import numpy as np
from numpy.typing import NDArray
from quri_parts.circuit import QuantumCircuit
from quri_parts.circuit.inverse import inverse_circuit
from scikit_quri.backend import BaseSampler
[docs]class OverlapEstimator:
"""Alternative implementation of quri-parts' overlap estimator."""
def __init__(self, concurrent_sampler: BaseSampler, n_shots: int = 1000):
"""
Args:
concurrent_sampler: Sampling backend (e.g. ``QulacsSampler`` or ``OqtopusSampler``).
n_shots: Number of shots per circuit execution. Defaults to 1000.
"""
self.concurrent_sampler = concurrent_sampler
self.n_shots = n_shots
[docs] def create_overlap_circuit(
self, ket_circuit: QuantumCircuit, bra_circuit: QuantumCircuit
) -> QuantumCircuit:
"""Create a circuit to compute the overlap between two quantum states.
Operates non-destructively on the input circuits.
Args:
ket_circuit: Quantum circuit representing the ket state.
bra_circuit: Quantum circuit representing the bra state.
Returns:
A quantum circuit of the form U_ket U_bra†, whose |0⟩ measurement
probability approximates |⟨ψ_ket|ψ_bra⟩|².
"""
ket_circuit = ket_circuit.get_mutable_copy()
bra_circuit = bra_circuit.get_mutable_copy()
return ket_circuit + inverse_circuit(bra_circuit)
[docs] def estimate(self, ket_circuit: QuantumCircuit, bra_circuit: QuantumCircuit) -> float:
"""Estimate the squared overlap |⟨ψ_ket|ψ_bra⟩|² between two quantum states.
Operates non-destructively on the input circuits.
Args:
ket_circuit: Quantum circuit representing the ket state.
bra_circuit: Quantum circuit representing the bra state.
Returns:
Estimated value of |⟨ψ_ket|ψ_bra⟩|².
"""
circuit = self.create_overlap_circuit(ket_circuit, bra_circuit)
sampling_count = list(self.concurrent_sampler([(circuit, self.n_shots)]))
count_zero = sampling_count[0].get(0)
if not count_zero:
count_zero = 0
p = count_zero / self.n_shots
return p
[docs] def estimate_concurrent(
self,
ket_circuits: list[QuantumCircuit],
bra_circuits: list[QuantumCircuit],
batch_size: int = 100,
) -> NDArray[np.float64]:
"""Estimate |⟨ψ_i|ψ_j⟩|² for all combinations of ket and bra circuits.
Args:
ket_circuits: List of quantum circuits representing ket states.
bra_circuits: List of quantum circuits representing bra states.
batch_size: Number of circuits sent to the sampler at once. Bounds
peak memory at O(batch_size) instead of O(n_ket * n_bra).
Returns:
Flat array of shape (n_ket * n_bra,) containing the squared overlaps
for all (ket, bra) pairs in row-major order.
"""
n_ket = len(ket_circuits)
n_bra = len(bra_circuits)
total = n_ket * n_bra
# Symmetric case: caller passed the same list for ket and bra
# (e.g. Gram matrix construction during fit). Compute only the strict
# upper triangle, set the diagonal to 1.0, mirror to the lower triangle.
if ket_circuits is bra_circuits and n_ket == n_bra:
n = n_ket
# Upper-triangle pair indices (i < j)
iu, ju = np.triu_indices(n, k=1)
n_pairs = iu.size
upper = np.empty(n_pairs, dtype=np.float64)
for start in range(0, n_pairs, batch_size):
end = min(start + batch_size, n_pairs)
batch = [
(
self.create_overlap_circuit(
ket_circuits[int(iu[idx])], bra_circuits[int(ju[idx])]
),
self.n_shots,
)
for idx in range(start, end)
]
sampling_counts = self.concurrent_sampler(batch)
for k, count in enumerate(sampling_counts):
upper[start + k] = count.get(0, 0) / self.n_shots
matrix = np.eye(n, dtype=np.float64)
matrix[iu, ju] = upper
matrix[ju, iu] = upper
return matrix.ravel()
overlaps = np.empty(total, dtype=np.float64)
for start in range(0, total, batch_size):
end = min(start + batch_size, total)
batch = [
(
self.create_overlap_circuit(
ket_circuits[idx // n_bra], bra_circuits[idx % n_bra]
),
self.n_shots,
)
for idx in range(start, end)
]
sampling_counts = self.concurrent_sampler(batch)
for k, count in enumerate(sampling_counts):
overlaps[start + k] = count.get(0, 0) / self.n_shots
return overlaps