English
Languages
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

# Source code for qiskit.algorithms.phase_estimators.ipe

# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
#
# obtain a copy of this license in the LICENSE.txt file in the root directory
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The Iterative Quantum Phase Estimation Algorithm."""

from __future__ import annotations

import warnings

import numpy

import qiskit
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit.classicalregister import ClassicalRegister
from qiskit.providers import Backend
from qiskit.utils import QuantumInstance
from qiskit.algorithms.exceptions import AlgorithmError
from .phase_estimator import PhaseEstimator
from .phase_estimator import PhaseEstimatorResult
from ...primitives import BaseSampler

[docs]class IterativePhaseEstimation(PhaseEstimator):
"""Run the Iterative quantum phase estimation (QPE) algorithm.

Given a unitary circuit and a circuit preparing an eigenstate, return the phase of the
eigenvalue as a number in :math:[0,1) using the iterative phase estimation algorithm.

: Dobsicek et al. (2006), Arbitrary accuracy iterative phase estimation algorithm as a two
qubit benchmark, arxiv/quant-ph/0610214 <https://arxiv.org/abs/quant-ph/0610214>_
"""

def __init__(
self,
num_iterations: int,
quantum_instance: QuantumInstance | Backend | None = None,
sampler: BaseSampler | None = None,
) -> None:
r"""Args:
num_iterations: The number of iterations (rounds) of the phase estimation to run.
quantum_instance: Pending deprecation\: The quantum instance on which the
circuit will be run.
sampler: The sampler primitive on which the circuit will be sampled.

Raises:
ValueError: if num_iterations is not greater than zero.
AlgorithmError: If neither sampler nor quantum instance is provided.
"""
if sampler is None and quantum_instance is None:
raise AlgorithmError(
"Neither a sampler nor a quantum instance was provided. Please provide one of them."
)
if quantum_instance is not None:
warnings.warn(
"The quantum_instance argument has been superseded by the sampler argument. "
"This argument will be deprecated in a future release and subsequently "
"removed after that.",
category=PendingDeprecationWarning,
)
if isinstance(quantum_instance, Backend):
quantum_instance = QuantumInstance(quantum_instance)
self._quantum_instance = quantum_instance
if num_iterations <= 0:
raise ValueError("num_iterations must be greater than zero.")
self._num_iterations = num_iterations
self._sampler = sampler

[docs]    def construct_circuit(
self,
unitary: QuantumCircuit,
state_preparation: QuantumCircuit,
k: int,
omega: float = 0.0,
measurement: bool = False,
) -> QuantumCircuit:
"""Construct the kth iteration Quantum Phase Estimation circuit.

For details of parameters, see Fig. 2 in https://arxiv.org/pdf/quant-ph/0610214.pdf.

Args:
unitary: The circuit representing the unitary operator whose eigenvalue (via phase)
will be measured.
state_preparation: The circuit that prepares the state whose eigenphase will be
measured.  If this parameter is omitted, no preparation circuit
will be run and input state will be the all-zero state in the
computational basis.
k: the iteration idx.
omega: the feedback angle.
measurement: Boolean flag to indicate if measurement should
be included in the circuit.

Returns:
QuantumCircuit: the quantum circuit per iteration
"""
k = self._num_iterations if k is None else k
# The auxiliary (phase measurement) qubit
phase_register = QuantumRegister(1, name="a")
eigenstate_register = QuantumRegister(unitary.num_qubits, name="q")
qc = QuantumCircuit(eigenstate_register)
if isinstance(state_preparation, QuantumCircuit):
qc.append(state_preparation, eigenstate_register)
elif state_preparation is not None:
qc += state_preparation.construct_circuit("circuit", eigenstate_register)
qc.h(phase_register)
# controlled-U
# TODO: We may want to allow flexibility in how the power is computed
# For example, it may be desirable to compute the power via Trotterization, if
# we are doing Trotterization anyway.
unitary_power = unitary.power(2 ** (k - 1)).control()
qc = qc.compose(unitary_power, list(range(1, unitary.num_qubits + 1)) + )
qc.p(omega, phase_register)
qc.h(phase_register)
if measurement:
c = ClassicalRegister(1, name="c")
qc.measure(phase_register, c)
return qc

def _estimate_phase_iteratively(self, unitary, state_preparation):
"""
Main loop of iterative phase estimation.
"""
omega_coef = 0
# k runs from the number of iterations back to 1
for k in range(self._num_iterations, 0, -1):
omega_coef /= 2

if self._sampler is not None:

qc = self.construct_circuit(
unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True
)
try:
sampler_job = self._sampler.run([qc])
result = sampler_job.result().quasi_dists
except Exception as exc:
raise AlgorithmError("The primitive job failed!") from exc
x = 1 if result.get(1, 0) > result.get(0, 0) else 0

elif self._quantum_instance.is_statevector:
qc = self.construct_circuit(
unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=False
)
result = self._quantum_instance.execute(qc)
complete_state_vec = result.get_statevector(qc)
ancilla_density_mat = qiskit.quantum_info.partial_trace(
complete_state_vec, range(unitary.num_qubits)
)
ancilla_density_mat_diag = numpy.diag(ancilla_density_mat)
max_amplitude = max(
ancilla_density_mat_diag.min(), ancilla_density_mat_diag.max(), key=abs
)
x = numpy.where(ancilla_density_mat_diag == max_amplitude)
else:
qc = self.construct_circuit(
unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=True
)
measurements = self._quantum_instance.execute(qc).get_counts(qc)
x = 1 if measurements.get("1", 0) > measurements.get("0", 0) else 0
omega_coef = omega_coef + x / 2
return omega_coef

# pylint: disable=signature-differs
[docs]    def estimate(
self, unitary: QuantumCircuit, state_preparation: QuantumCircuit
) -> "IterativePhaseEstimationResult":
"""
Estimate the eigenphase of the input unitary and initial-state pair.

Args:
unitary: The circuit representing the unitary operator whose eigenvalue (via phase)
will be measured.
state_preparation: The circuit that prepares the state whose eigenphase will be
measured.  If this parameter is omitted, no preparation circuit
will be run and input state will be the all-zero state in the
computational basis.

Returns:
Estimated phase in an IterativePhaseEstimationResult object.

Raises:
AlgorithmError: If neither sampler nor quantum instance is provided.
"""
phase = self._estimate_phase_iteratively(unitary, state_preparation)

return IterativePhaseEstimationResult(self._num_iterations, phase)

class IterativePhaseEstimationResult(PhaseEstimatorResult):
"""Phase Estimation Result."""

def __init__(self, num_iterations: int, phase: float) -> None:
"""
Args:
num_iterations: number of iterations used in the phase estimation.
phase: the estimated phase.
"""

self._num_iterations = num_iterations
self._phase = phase

@property
def phase(self) -> float:
r"""Return the estimated phase as a number in :math:[0.0, 1.0).

1.0 corresponds to a phase of :math:2\pi. It is assumed that the input vector is an
eigenvector of the unitary so that the peak of the probability density occurs at the bit
string that most closely approximates the true phase.
"""
return self._phase

@property
def num_iterations(self) -> int:
r"""Return the number of iterations used in the estimation algorithm."""
return self._num_iterations