"""Iterative phase estimation circuit builder.
This module implements the circuit-building component of the Kitaev-style iterative
quantum phase estimation (IQPE) algorithm. It constructs the iteration circuits
without executing them, enabling standalone resource estimation and circuit preview.
"""
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from qdk_chemistry.data import AlgorithmRef, Circuit, QubitOperator
from qdk_chemistry.data.circuit import QsharpFactoryData
from qdk_chemistry.utils import Logger
from qdk_chemistry.utils.qsharp import QSHARP_UTILS
from .base import IterativeQpeCircuitBuilder, QpeCircuitBuilderSettings
__all__: list[str] = [
"QdkIterativeQpeCircuitBuilder",
"QdkIterativeQpeCircuitBuilderSettings",
"_validate_iteration_inputs",
]
[docs]
class QdkIterativeQpeCircuitBuilderSettings(QpeCircuitBuilderSettings):
"""Settings for the Iterative Phase Estimation Builder."""
[docs]
def __init__(self):
"""Initialize the settings for the Iterative Phase Estimation Builder."""
super().__init__()
self._set_default("phase_correction", "double", 0.0, "The accumulated phase feedback from prior iterations.")
self._set_default(
"num_iteration", "int", -1, "The specific iteration to build. Default to -1 to build all iterations."
)
[docs]
class QdkIterativeQpeCircuitBuilder(IterativeQpeCircuitBuilder):
"""Iterative Phase Estimation circuit builder.
Constructs the quantum circuits for each IQPE iteration without executing them.
Can be used standalone for resource estimation or composed inside IterativePhaseEstimation.
"""
[docs]
def __init__(
self,
num_bits: int = -1,
phase_correction: float = 0.0,
num_iteration: int = -1,
unitary_builder: AlgorithmRef | None = None,
controlled_circuit_mapper: AlgorithmRef | None = None,
):
"""Initialize the IterativeQpeCircuitBuilder.
Args:
num_bits: The number of phase bits to estimate. Default to -1; user needs to set a valid value.
phase_correction: The accumulated phase feedback from prior iterations. Default to 0.0.
num_iteration: The specific iteration to build. Default to -1 (build all iterations).
unitary_builder: Optional algorithm reference for the unitary builder.
controlled_circuit_mapper: Optional algorithm reference for the controlled circuit mapper.
"""
Logger.trace_entering()
super().__init__(num_bits=num_bits)
self._settings = QdkIterativeQpeCircuitBuilderSettings()
self._settings.set("num_bits", num_bits)
self._settings.set("phase_correction", phase_correction)
self._settings.set("num_iteration", num_iteration)
if unitary_builder is not None:
self._settings.set("unitary_builder", unitary_builder)
if controlled_circuit_mapper is not None:
self._settings.set("controlled_circuit_mapper", controlled_circuit_mapper)
def _run_impl(
self,
state_preparation: Circuit,
qubit_hamiltonian: QubitOperator,
) -> list[Circuit]:
"""Build IQPE iteration circuits.
Uses settings ``phase_correction`` (default 0.0) and ``num_iteration``
(default -1). When ``num_iteration`` is negative, all iteration circuits
are returned. When positive, only the circuit for that single iteration
(0-based) is returned.
Args:
state_preparation: The circuit that prepares the initial state.
qubit_hamiltonian: The qubit Hamiltonian for which to build circuits.
Returns:
A list of quantum circuits, one per phase bit iteration (or a single-element
list when ``num_iteration`` is set to a specific iteration index).
Raises:
ValueError: If ``num_iteration`` >= ``num_bits``.
"""
num_bits = self.settings().get("num_bits")
if num_bits <= 0:
raise ValueError(f"num_bits must be a positive integer. Got {num_bits}.")
phase_correction = self.settings().get("phase_correction")
num_iteration = self.settings().get("num_iteration")
if num_iteration >= num_bits:
raise ValueError(f"num_iteration ({num_iteration}) must be less than num_bits ({num_bits}).")
iterations = [num_iteration] if num_iteration >= 0 else range(num_bits)
circuits: list[Circuit] = []
for iteration in iterations:
circuit = self._create_iteration_circuit(
state_preparation=state_preparation,
qubit_hamiltonian=qubit_hamiltonian,
iteration=iteration,
total_iterations=num_bits,
phase_correction=phase_correction,
)
circuits.append(circuit)
Logger.info(f"Built {len(circuits)} iteration circuit(s) with phase_correction={phase_correction}.")
return circuits
def _create_iteration_circuit(
self,
state_preparation: Circuit,
qubit_hamiltonian: QubitOperator,
*,
iteration: int,
total_iterations: int,
phase_correction: float = 0.0,
) -> Circuit:
"""Construct a single IQPE iteration circuit.
Args:
state_preparation: Trial-state preparation circuit that prepares the initial state on the system qubits.
qubit_hamiltonian: The qubit Hamiltonian for which to estimate the phase.
iteration: Current iteration index (0-based), where 0 corresponds to the most-significant bit.
total_iterations: Total number of phase bits to measure across all iterations.
phase_correction: Feedback phase angle to apply before controlled unitary, defaults to 0.0.
Returns:
A quantum circuit implementing one IQPE iteration.
"""
_validate_iteration_inputs(iteration, total_iterations)
num_system_qubits = qubit_hamiltonian.num_qubits
power = 2 ** (total_iterations - iteration - 1)
ctrl_unitary_circuit, num_ancilla_qubits = self._create_controlled_circuit(qubit_hamiltonian, power)
if state_preparation._qsharp_op and ctrl_unitary_circuit._qsharp_op: # noqa: SLF001
return self._create_circuit_from_qsharp_op(
state_preparation, ctrl_unitary_circuit, phase_correction, num_system_qubits, num_ancilla_qubits
)
raise RuntimeError(
"Failed to create iteration circuit: Q# operations are not available. "
"For Qiskit support, use QiskitIterativeQpeCircuitBuilder from the qiskit plugin."
)
def _create_circuit_from_qsharp_op(
self,
state_preparation: Circuit,
controlled_unitary_circuit: Circuit,
phase_correction: float,
num_system_qubits: int,
num_ancilla_qubits: int = 0,
) -> Circuit:
"""Create a Circuit object from a Q# operation.
Args:
state_preparation: Circuit object containing a Q# operation for state preparation.
controlled_unitary_circuit: Circuit object containing a Q# operation for the controlled unitary.
phase_correction: Feedback phase angle to apply before controlled unitary.
num_system_qubits: Number of system qubits.
num_ancilla_qubits: Number of ancilla qubits within the unitary (0 for Trotter).
Returns:
A Circuit object representing the IQPE iteration.
"""
state_prep_op = state_preparation._qsharp_op # noqa: SLF001
ctrl_unitary_op = controlled_unitary_circuit._qsharp_op # noqa: SLF001
iterative_parameters = {
"statePrep": state_prep_op,
"repControlledUnitary": ctrl_unitary_op,
"accumulatePhase": phase_correction,
"phaseQubit": 0,
"systems": [i + 1 for i in range(num_system_qubits)],
"numAncillaQubits": num_ancilla_qubits,
}
return Circuit(
qsharp_factory=QsharpFactoryData(
program=QSHARP_UTILS.IterativePhaseEstimation.MakeIQPECircuit,
parameter=iterative_parameters,
)
)
[docs]
def name(self) -> str:
"""Return the name of the builder algorithm."""
return "qdk_iterative"
def _validate_iteration_inputs(iteration: int, total_iterations: int) -> None:
"""Validate iteration parameters for IQPE circuit construction.
Args:
iteration: The current iteration index (0-based).
total_iterations: The total number of iterations.
"""
if total_iterations <= 0:
raise ValueError("total_iterations must be a positive integer.")
if iteration < 0 or iteration >= total_iterations:
raise ValueError(
f"iteration index {iteration} is outside the valid range [0, {total_iterations - 1}].",
)