Source code for qarp.blocks._primitives.qpe_block
from copy import deepcopy
from typing import List, Optional
from ..._structure import Repeat
from .._block import AnyBlock, CompositeBlockBase, ControlledBlock
from .._primitives import QFTBlock
from .hn_block import HnBlock
from .readout_block import ReadoutBlock
[docs]
class QPEBlock(CompositeBlockBase):
"""Quantum Phase Estimation (QPE) circuit block — Pattern B composite.
Composes: ancilla Hadamards · state prep · controlled-U^(2^i) ladder
· inverse QFT · (optional) ancilla measurements.
"""
def __init__(
self,
eigenstate: AnyBlock,
unitary: AnyBlock,
n_ancilla: int,
n_state: int,
measure: bool = True,
target_qubits: Optional[List[int]] = None,
name: str = "QPE",
):
"""Quantum Phase Estimation: controlled-U powers on an ancilla
register followed by an inverse QFT.
Args:
eigenstate: Block preparing the probe state (on the state register).
unitary: Block of the unitary operator.
n_ancilla: Number of ancilla qubits (phase precision bits).
n_state: Number of qubits in the eigenstate circuit.
measure: If True, add Measure commands on the ancilla register.
target_qubits, name: see ``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 + n_state,
target_qubits=target_qubits,
name=name,
)
[docs]
def structure(self) -> list:
"""Hadamards on the ancillas, the eigenstate preparation, 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()
ancilla_qubits = list(range(self.n_ancilla))
state_qubits = list(range(self.n_ancilla, self.n_qubits))
parts: list = [
HnBlock(self.n_ancilla, target_qubits=ancilla_qubits, name="AncillaH").build()
]
eigen_built.target_qubits = state_qubits
parts.append(eigen_built)
# Ancilla i gets 2^i applications of the controlled unitary → phase
# kickback 2^i·φ, so the ancilla register encodes the phase as an
# integer (qubit 0 = LSB).
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)