Source code for qdk_chemistry.algorithms.phase_estimation.iterative_phase_estimation

"""Iterative phase estimation implementation.

This module implements the Kitaev-style iterative quantum phase estimation (IQPE)
algorithm, which measures phase bits sequentially from most-significant to least-significant
using a single ancilla qubit and adaptive feedback corrections.

References:
    Kitaev, A. (1995). arXiv:quant-ph/9511026. :cite:`Kitaev1995`

"""

# --------------------------------------------------------------------------------------------
# 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.algorithms.circuit_executor.base import CircuitExecutor
from qdk_chemistry.algorithms.time_evolution.builder.base import TimeEvolutionBuilder
from qdk_chemistry.algorithms.time_evolution.controlled_circuit_mapper.base import ControlledEvolutionCircuitMapper
from qdk_chemistry.data import (
    Circuit,
    ControlledTimeEvolutionUnitary,
    QpeResult,
    QuantumErrorProfile,
    QubitHamiltonian,
)
from qdk_chemistry.data.circuit import QsharpFactoryData
from qdk_chemistry.utils import Logger
from qdk_chemistry.utils.phase import iterative_phase_feedback_update, phase_fraction_from_feedback
from qdk_chemistry.utils.qsharp import QSHARP_UTILS

from .base import PhaseEstimation, PhaseEstimationSettings

__all__: list[str] = ["IterativePhaseEstimation", "IterativePhaseEstimationSettings"]


[docs] class IterativePhaseEstimationSettings(PhaseEstimationSettings): """Settings for the Iterative Phase Estimation algorithm."""
[docs] def __init__(self): """Initialize the settings for Iterative Phase Estimation. Args: shots_per_bit: The number of shots to execute per measuring a bit in the iterative phase estimation. """ super().__init__() self._set_default( "shots_per_bit", "int", 3, "The number of shots to execute per measuring a bit in the iterative phase estimation.", )
[docs] class IterativePhaseEstimation(PhaseEstimation): """Iterative Phase Estimation algorithm implementation."""
[docs] def __init__( self, num_bits: int = -1, evolution_time: float = 0.0, shots_per_bit: int = 3, ): """Initialize IterativePhaseEstimation with the given settings. Args: num_bits: The number of phase bits to estimate. Default to -1; user needs to set a valid value. evolution_time: Time parameter ``t`` used in the time-evolution unitary ``U = exp(-i H t)``, defaults to 0.0; user needs to set a valid value. shots_per_bit: The number of shots to execute per measuring a bit in the iterative phase estimation. """ Logger.trace_entering() super().__init__(num_bits=num_bits, evolution_time=evolution_time) self._settings = IterativePhaseEstimationSettings() self._settings.set("num_bits", num_bits) self._settings.set("evolution_time", evolution_time) self._settings.set("shots_per_bit", shots_per_bit) self._iteration_circuits: list[Circuit] | None = None
def _run_impl( self, state_preparation: Circuit, qubit_hamiltonian: QubitHamiltonian, *, evolution_builder: TimeEvolutionBuilder, circuit_mapper: ControlledEvolutionCircuitMapper, circuit_executor: CircuitExecutor, noise: QuantumErrorProfile | None = None, ) -> QpeResult: """Run the iterative phase estimation algorithm with the given state preparation circuit and qubit Hamiltonian. Args: state_preparation: The state preparation circuit. qubit_hamiltonian: The qubit Hamiltonian for which to estimate the phase. evolution_builder: The time evolution builder to use. circuit_mapper: The controlled evolution circuit mapper to use. circuit_executor: The executor to run quantum circuits. noise: The quantum error profile to simulate noise, defaults to None. Returns: QpeResult: The result of the phase estimation. """ # Initialize the parameters phase_feedback = 0.0 bits: list[int] = [] iter_circuits: list[Circuit] = [] # Iterate over the number of phase bits for iteration in range(self.settings().get("num_bits")): # Create the iteration circuit iteration_circuit = self.create_iteration_circuit( state_preparation=state_preparation, qubit_hamiltonian=qubit_hamiltonian, evolution_builder=evolution_builder, circuit_mapper=circuit_mapper, iteration=iteration, total_iterations=self.settings().get("num_bits"), phase_correction=phase_feedback, ) iter_circuits.append(iteration_circuit) Logger.info(f"Iteration {iteration + 1} / {self.settings().get('num_bits')}: circuit generated.") # Run the iteration circuit on the simulator executor_data = circuit_executor.run( iteration_circuit, shots=self.settings().get("shots_per_bit"), noise=noise ) bitstring_result = executor_data.bitstring_counts Logger.info( f"Iteration {iteration + 1} / {self.settings().get('num_bits')}: " f"Measurement results: {bitstring_result}" ) # Phase bit through majority vote measured_bit = 0 if bitstring_result.get("0", 0) >= bitstring_result.get("1", 0) else 1 Logger.debug(f"Majority measured bit: {measured_bit}") # Store the measured bit bits.append(measured_bit) # Update the phase feedback for next iteration phase_feedback = iterative_phase_feedback_update(phase_feedback, measured_bit) # Compute the final phase fraction phase_fraction = phase_fraction_from_feedback(phase_feedback) self._iteration_circuits = iter_circuits # Create and return the result return QpeResult.from_phase_fraction( method=self.name(), phase_fraction=phase_fraction, evolution_time=self.settings().get("evolution_time"), bits_msb_first=bits, )
[docs] def create_iteration_circuit( self, state_preparation: Circuit, qubit_hamiltonian: QubitHamiltonian, *, evolution_builder: TimeEvolutionBuilder, circuit_mapper: ControlledEvolutionCircuitMapper, 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. evolution_builder: The time evolution builder to use. circuit_mapper: The controlled evolution circuit mapper to use. 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 evolution, defaults to 0.0. Returns: A quantum circuit implementing one IQPE iteration. """ _validate_iteration_inputs(iteration, total_iterations) # Build the base circuit with registers num_system_qubits = qubit_hamiltonian.num_qubits time_evolution_unitary = self._create_time_evolution( qubit_hamiltonian, self.settings().get("evolution_time"), evolution_builder ) controlled_evolution = ControlledTimeEvolutionUnitary( time_evolution_unitary=time_evolution_unitary, control_indices=[0] ) power = 2 ** (total_iterations - iteration - 1) ctrl_evol_circuit = self._create_ctrl_time_evol_circuit(controlled_evolution, power, circuit_mapper) if state_preparation._qsharp_op and ctrl_evol_circuit._qsharp_op: # noqa: SLF001 return self._create_circuit_from_qsharp_op( state_preparation, ctrl_evol_circuit, phase_correction, num_system_qubits ) if state_preparation.get_qiskit_circuit() and ctrl_evol_circuit.get_qiskit_circuit(): return self._create_circuit_from_qiskit(state_preparation, ctrl_evol_circuit, phase_correction) raise RuntimeError( "Failed to create iteration circuit: Q# operations or Qiskit dependencies are not available." )
def _create_circuit_from_qsharp_op( self, state_preparation: Circuit, controlled_evolution: Circuit, phase_correction: float, num_system_qubits: int ) -> Circuit: """Create a Circuit object from a Q# operation. Args: state_preparation: Circuit object containing a Q# operation for state preparation. controlled_evolution: Circuit object containing a Q# operation for the controlled time evolution. phase_correction: Feedback phase angle to apply before controlled evolution. num_system_qubits: Number of system qubits. Returns: A Circuit object representing the IQPE iteration. """ state_prep_op = state_preparation._qsharp_op # noqa: SLF001 ctrl_evol_op = controlled_evolution._qsharp_op # noqa: SLF001 iterative_parameters = { "statePrep": state_prep_op, "repControlledEvolution": ctrl_evol_op, "accumulatePhase": phase_correction, "control": 0, "systems": [i + 1 for i in range(num_system_qubits)], } return Circuit( qsharp_factory=QsharpFactoryData( program=QSHARP_UTILS.IterativePhaseEstimation.MakeIQPECircuit, parameter=iterative_parameters, ) ) def _create_circuit_from_qiskit( self, state_preparation: Circuit, controlled_evolution: Circuit, phase_correction: float ) -> Circuit: """Create a Circuit object from Qiskit QuantumCircuit objects. Args: state_preparation: Circuit object containing a Qiskit QuantumCircuit for state preparation. controlled_evolution: Circuit object containing a Qiskit QuantumCircuit for the controlled time evolution. phase_correction: Feedback phase angle to apply before controlled evolution. Returns: A Circuit object representing the IQPE iteration. """ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, qasm3 # noqa: PLC0415 state_prep_qc = state_preparation.get_qiskit_circuit() ctrl_evol_qc = controlled_evolution.get_qiskit_circuit() # Parse the state preparation circuit from QASM ancilla = QuantumRegister(1, "ancilla") system_target = QuantumRegister(state_prep_qc.num_qubits, "system") classical = ClassicalRegister(1, "c") circuit = QuantumCircuit(ancilla, system_target, classical) circuit.append(state_prep_qc.to_gate(), system_target) control = ancilla[0] target_qubits = list(system_target) circuit.h(control) # Apply phase correction if provided if phase_correction: circuit.rz(phase_correction, control) # Append the controlled evolution circuit circuit.append(ctrl_evol_qc.to_gate(), [control, *target_qubits]) circuit.h(control) circuit.measure(control, classical[0]) return Circuit(qasm=qasm3.dumps(circuit))
[docs] def get_circuits(self) -> list[Circuit]: """Get the list of iteration circuits generated during algorithm execution. Returns: List of quantum circuits for each IQPE iteration. Raises: ValueError: If no iteration circuits are available. """ if self._iteration_circuits is not None: return self._iteration_circuits raise ValueError("No iteration circuits have been generated. Please run the algorithm first.")
[docs] def name(self) -> str: """Return the name of the phase estimation algorithm.""" return "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}].", )