Source code for qarp.blocks._primitives.dos_qpe_block

from copy import deepcopy
from typing import List, Optional

from ..._structure import Repeat
from .._block import AnyBlock, CompositeBlockBase, ControlledBlock, SimpleBlock
from .._primitives import QFTBlock
from .hn_block import HnBlock
from .readout_block import ReadoutBlock


[docs] class DOSQPEBlock(CompositeBlockBase): """Density Of States Quantum Phase Estimation (DOSQPE) circuit block. Composes: ancilla Hadamards · eigenstate prep · CNOT purification entanglement · controlled-U^(2^i) ladder (on state register) · inverse QFT · (optional) ancilla measurements. Total qubits: ``n_ancilla + 2 * n_state``. The registers are laid out as: [0 .. n_ancilla-1] — ancilla (time/frequency) [n_ancilla .. n_ancilla+n_state-1] — state [n_ancilla+n_state .. n_q-1] — purification (traced out) References: arXiv:2510.14744 """ def __init__( self, eigenstate: AnyBlock, unitary: AnyBlock, n_ancilla: int, n_state: int, measure: bool = False, target_qubits: Optional[List[int]] = None, name: str = "DOSQPE", ): """ Density Of States Quantum Phase Estimation (DOSQPE) circuit class (arXiv:2510.14744). Args: eigenstate: Block that prepares the eigenstate (probe state). unitary: Block representing the unitary operator whose DOS is to be estimated. n_ancilla: Number of ancilla qubits for phase estimation. n_state: Number of qubits in the state register. measure: Whether to measure the ancilla qubits at the end. target_qubits: Optional list of target qubits for controlled operations. name: Name of the block. """ self.eigenstate = eigenstate self.unitary = unitary self.n_ancilla = n_ancilla self.n_state = n_state self.measure_at_end = measure self._parts: Optional[list] = None super().__init__( n_qubits=n_ancilla + 2 * n_state, target_qubits=target_qubits, name=name, )
[docs] def structure(self) -> list: """Hadamards on the ancillas, the probe preparation, the CNOT layer that entangles it with the purification register, for ancilla ``i`` the controlled unitary repeated ``2**i`` times, the inverse QFT, and the ancilla readout (§13). Builds only its parts, once.""" if self._parts is not None: return self._parts # The parts are placed in this block's frame; the caller's block is # never moved, so it can serve another block unchanged. eigen_built = deepcopy(self.eigenstate.build()) unit_built = self.unitary.build() n_q = self.n_qubits # n_ancilla + 2 * n_state ancilla_qubits = list(range(self.n_ancilla)) state_qubits = list(range(self.n_ancilla, self.n_ancilla + self.n_state)) purification_qubits = list(range(self.n_ancilla + self.n_state, n_q)) parts: list = [ HnBlock(self.n_ancilla, target_qubits=ancilla_qubits, name="AncillaH").build() ] eigen_built.target_qubits = state_qubits parts.append(eigen_built) # Tracing out the purification register gives the desired mixed probe. cnot_layer = SimpleBlock(2 * self.n_state, name="PurificationCNOT") for i in range(self.n_state): cnot_layer.cx(i, self.n_state + i) cnot_layer.target_qubits = state_qubits + purification_qubits parts.append(cnot_layer.build()) # Phase kickback 2^i·φ on ancilla i. for i, ancilla_q in enumerate(ancilla_qubits): ctrl_u = ControlledBlock( unit_built, num_controls=1, ctrl_state=[True], name=f"C-U@a{ancilla_q}" ) ctrl_u.build() ctrl_u.target_qubits = [ancilla_q] + state_qubits parts.append(Repeat(ctrl_u, 2**i)) iqft = QFTBlock(self.n_ancilla).dagger().build() iqft.target_qubits = ancilla_qubits parts.append(iqft) if self.measure_at_end: parts.append( ReadoutBlock( self.n_ancilla, target_qubits=ancilla_qubits, name="AncillaMeas" ).build() ) self._parts = parts return parts
[docs] def build_vanilla(self) -> None: for part in self.structure(): if isinstance(part, Repeat): for _ in range(part.count): self.add_child(part.block) else: self.add_child(part)