import numpy as np
from qutip import Qobj
from qutip_qip.typing import IntSequence
from qutip_qip.algorithms import qft_gate_sequence
from qutip_qip.circuit import QubitCircuit
from qutip_qip.operations import get_unitary_gate, get_controlled_gate
from qutip_qip.operations.gates import H
[docs]
def qpe(
U: Qobj,
num_counting_qubits: int,
target_qubits: int | IntSequence | None = None,
to_cnot: bool = False,
) -> QubitCircuit:
"""
Quantum Phase Estimation circuit implementation for QuTiP.
Parameters
----------
U : Qobj
Unitary operator whose eigenvalue we want to estimate.
Should be a unitary quantum operator.
num_counting_qubits : int
Number of counting qubits to use for the phase estimation.
More qubits provide higher precision.
target_qubits : int or sequence of int, optional
Index or indices of the target qubit(s) where the eigenstate is prepared.
If None, target_qubits is set automatically based on U's dimension.
to_cnot : bool, optional
Flag to decompose controlled phase gates to CNOT gates (default: False)
Returns
-------
qc : :class:`.QubitCircuit`
Gate sequence implementing Quantum Phase Estimation.
"""
if num_counting_qubits < 1:
raise ValueError("Minimum value of counting qubits must be 1")
# Handle target qubits specification
if target_qubits is None:
dim = U.shape[0]
num_target_qubits = int(np.log2(dim))
if 1 << num_target_qubits != dim:
raise ValueError(f"Unitary operator dimension {dim} is not a power of 2")
target_qubits = list(
range(num_counting_qubits, num_counting_qubits + num_target_qubits)
)
elif type(target_qubits) is int:
target_qubits = [target_qubits]
num_target_qubits = 1
else:
num_target_qubits = len(target_qubits)
total_qubits = num_counting_qubits + num_target_qubits
qc = QubitCircuit(total_qubits)
# Apply Hadamard gates to all counting qubits
for i in range(num_counting_qubits):
qc.add_gate(H, targets=[i])
# Apply controlled-U gates with increasing powers
for i in range(num_counting_qubits):
power = 2 ** (num_counting_qubits - i - 1)
# Create U^power
U_power = U if power == 1 else U**power
# Add controlled-U^power gate
controlled_u = get_controlled_gate(
gate=get_unitary_gate(
gate_name=f"U^{power}",
U=U_power,
),
)
qc.add_gate(controlled_u, targets=target_qubits, controls=[i])
# Add inverse QFT on counting qubits
qc.add_circuit(
qft_gate_sequence(
num_counting_qubits, swapping=True, to_cnot=to_cnot
).reverse_circuit()
)
return qc