Source code for qarp.blocks._primitives.dos_qpe_block
from typing import List, Optional
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
super().__init__(
n_qubits=n_ancilla + 2 * n_state,
target_qubits=target_qubits,
name=name,
)
[docs]
def build_vanilla(self):
"""Build the DOSQPE circuit as a qarpx CompositeBlock.
Returns:
qx.CompositeBlock: The assembled circuit.
"""
# Build children — each is a real qx.Block (Python wrapper).
eigen_built = 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))
# 1) Hadamard layer on ancilla (time/frequency) qubits
self.add_wired_child(HnBlock(self.n_ancilla, target_qubits=ancilla_qubits, name="AncillaH"))
# 2) Eigenstate (probe) prep on state register
eigen_built.target_qubits = state_qubits
self.add_child(eigen_built)
# 3) CNOT entanglement: state[i] → purification[i]. Tracing out the
# purification register gives the desired mixed probe state.
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
self.add_wired_child(cnot_layer)
# 4) Controlled-U^(2^i) ladder — phase kickback 2^i·φ on each ancilla qubit.
for i, ancilla_q in enumerate(ancilla_qubits):
for _ in range(2**i):
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
self.add_child(ctrl_u)
# 5) Inverse QFT on ancilla register
iqft = QFTBlock(self.n_ancilla).dagger().build()
iqft.target_qubits = ancilla_qubits
self.add_child(iqft)
# 6) Optional ancilla measurements — ancilla q reads into cbit q.
if self.measure_at_end:
self.add_wired_child(
ReadoutBlock(self.n_ancilla, target_qubits=ancilla_qubits, name="AncillaMeas")
)