"""Standard (QFT-based) phase estimation circuit builder.
This module implements the circuit-building component of the standard quantum phase
estimation (QPE) algorithm. It constructs a single circuit that uses multiple ancilla
qubits and the inverse QFT, 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 QpeCircuitBuilderSettings, StandardQpeCircuitBuilder
__all__: list[str] = [
"QdkStandardQpeCircuitBuilder",
"QdkStandardQpeCircuitBuilderSettings",
]
[docs]
class QdkStandardQpeCircuitBuilderSettings(QpeCircuitBuilderSettings):
"""Settings for the Standard Phase Estimation Circuit Builder."""
[docs]
def __init__(self):
"""Initialize the settings for the Standard Phase Estimation Circuit Builder."""
super().__init__()
[docs]
class QdkStandardQpeCircuitBuilder(StandardQpeCircuitBuilder):
"""Standard (QFT-based) Phase Estimation circuit builder.
Constructs a single quantum circuit that performs standard QPE using multiple
ancilla qubits and the inverse QFT. Can be used standalone for resource estimation
or composed inside StandardPhaseEstimation.
"""
[docs]
def __init__(
self,
num_bits: int = -1,
unitary_builder: AlgorithmRef | None = None,
controlled_circuit_mapper: AlgorithmRef | None = None,
):
"""Initialize the StandardQpeCircuitBuilder.
Args:
num_bits: The number of phase bits (ancilla qubits) to estimate. Default to -1;
user needs to set a valid value.
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 = QdkStandardQpeCircuitBuilderSettings()
self._settings.set("num_bits", num_bits)
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 the standard QPE circuit.
Constructs a single circuit with ``num_bits`` ancilla qubits, applying
controlled-U^(2^k) for each ancilla and finishing with the inverse QFT.
Args:
state_preparation: The circuit that prepares the initial state.
qubit_hamiltonian: The qubit Hamiltonian for which to build the circuit.
Returns:
A single-element list containing the standard QPE circuit.
Raises:
ValueError: If ``num_bits`` is not a positive integer.
"""
num_bits = self.settings().get("num_bits")
if num_bits <= 0:
raise ValueError(f"num_bits must be a positive integer. Got {num_bits}.")
num_system_qubits = qubit_hamiltonian.num_qubits
# Build one controlled circuit per ancilla with power=2^k,
# respecting the unitary builder's power_strategy (e.g. "rescale").
# ancillas[0] = MSB controls U^(2^(n-1)), ancillas[n-1] = LSB controls U^1.
ctrl_unitary_circuits = []
num_ancilla_qubits = 0
for k in range(num_bits):
power = 2 ** (num_bits - 1 - k)
circuit, num_ancilla_qubits = self._create_controlled_circuit(qubit_hamiltonian, power=power)
ctrl_unitary_circuits.append(circuit)
if state_preparation._qsharp_op and all(c._qsharp_op for c in ctrl_unitary_circuits): # noqa: SLF001
circuit = self._create_circuit_from_qsharp_op(
state_preparation, ctrl_unitary_circuits, num_bits, num_system_qubits, num_ancilla_qubits
)
Logger.info(f"Built standard QPE circuit with {num_bits} ancilla qubits.")
return [circuit]
raise RuntimeError(
"Failed to create standard QPE circuit: Q# operations are not available. "
"For Qiskit support, use QiskitStandardQpeCircuitBuilder from the qiskit plugin."
)
def _create_circuit_from_qsharp_op(
self,
state_preparation: Circuit,
controlled_unitary_circuits: list[Circuit],
num_bits: int,
num_system_qubits: int,
num_ancilla_qubits: int = 0,
) -> Circuit:
"""Create a Circuit object from a Q# operation using MakeStandardQPECircuit.
Args:
state_preparation: Circuit object containing a Q# operation for state preparation.
controlled_unitary_circuits: List of Circuit objects (one per ancilla) containing
Q# operations for controlled-U^(2^k).
num_bits: Number of ancilla qubits (phase bits).
num_system_qubits: Number of system qubits.
num_ancilla_qubits: Number of extra ancilla qubits within the unitary (0 for Trotter).
Returns:
A Circuit object representing the standard QPE circuit.
"""
state_prep_op = state_preparation._qsharp_op # noqa: SLF001
ctrl_unitary_ops = [c._qsharp_op for c in controlled_unitary_circuits] # noqa: SLF001
phase_qubit_prep_op = QSHARP_UTILS.StatePreparation.MakePrepareHadamardAllOp()
ancillas = list(range(num_bits))
systems = [i + num_bits for i in range(num_system_qubits)]
standard_parameters = {
"statePrep": state_prep_op,
"controlledUnitary": ctrl_unitary_ops,
"numBits": num_bits,
"ancillas": ancillas,
"systems": systems,
"phaseQubitPrep": phase_qubit_prep_op,
"numAncillaQubits": num_ancilla_qubits,
}
return Circuit(
qsharp_factory=QsharpFactoryData(
program=QSHARP_UTILS.StandardPhaseEstimation.MakeStandardQPECircuit,
parameter=standard_parameters,
)
)
[docs]
def name(self) -> str:
"""Return the name of the builder algorithm."""
return "qdk_standard"