# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# 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.
"""Quantum Phase Estimation for getting the eigenvalues of a matrix."""
import warnings
from typing import Optional, List, Union
import numpy as np
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.aqua.circuits import PhaseEstimationCircuit
from qiskit.aqua.operators import LegacyBaseOperator
from qiskit.aqua.operators.legacy import op_converter
from qiskit.aqua.components.iqfts import IQFT
from qiskit.aqua.utils.validation import validate_min, validate_in_set
from .eigs import Eigenvalues
# pylint: disable=invalid-name
[docs]class EigsQPE(Eigenvalues):
"""Eigenvalues using Quantum Phase Estimation.
Specifically, this class is based on PhaseEstimationCircuit with no measurements and
has additional handling of negative eigenvalues, e.g. for :class:`~qiskit.aqua.algorithms.HHL`.
It depends on :mod:`QFT <qiskit.aqua.components.qfts>` and
:mod:`IQFT <qiskit.aqua.components.iqfts>` components.
"""
def __init__(self,
operator: LegacyBaseOperator,
iqft: Union[QuantumCircuit, IQFT],
num_time_slices: int = 1,
num_ancillae: int = 1,
expansion_mode: str = 'trotter',
expansion_order: int = 1,
evo_time: Optional[float] = None,
negative_evals: bool = False,
ne_qfts: Optional[List] = None) -> None:
"""
Args:
operator: The Hamiltonian Operator object
iqft: The Inverse Quantum Fourier Transform component
num_time_slices: The number of time slices, has a minimum value of 1.
num_ancillae: The number of ancillary qubits to use for the measurement,
has a minimum value of 1.
expansion_mode: The expansion mode ('trotter' | 'suzuki')
expansion_order: The suzuki expansion order, has a minimum value of 1.
evo_time: An optional evolution time which should scale the eigenvalue onto the range
:math:`(0,1]` (or :math:`(-0.5,0.5]` for negative eigenvalues). Defaults to
``None`` in which case a suitably estimated evolution time is internally computed.
negative_evals: Set ``True`` to indicate negative eigenvalues need to be handled
ne_qfts: The QFT and IQFT components for handling negative eigenvalues
"""
super().__init__()
ne_qfts = ne_qfts if ne_qfts is not None else [None, None]
validate_min('num_time_slices', num_time_slices, 1)
validate_min('num_ancillae', num_ancillae, 1)
validate_in_set('expansion_mode', expansion_mode, {'trotter', 'suzuki'})
validate_min('expansion_order', expansion_order, 1)
self._operator = op_converter.to_weighted_pauli_operator(operator)
if isinstance(iqft, IQFT):
warnings.warn('Providing a qiskit.aqua.components.iqfts.IQFT module as `iqft` argument '
'to HHL is deprecated as of 0.7.0 and will be removed no earlier than '
'3 months after the release. '
'You should pass a QuantumCircuit instead, see '
'qiskit.circuit.library.QFT and the .inverse() method.',
DeprecationWarning, stacklevel=2)
self._iqft = iqft
self._num_ancillae = num_ancillae
self._num_time_slices = num_time_slices
self._expansion_mode = expansion_mode
self._expansion_order = expansion_order
self._evo_time = evo_time
self._negative_evals = negative_evals
if ne_qfts and any(isinstance(ne_qft, IQFT) for ne_qft in ne_qfts):
warnings.warn('Providing a qiskit.aqua.components.iqfts.IQFT module in the `ne_qft` '
'argument to HHL is deprecated as of 0.7.0 and will be removed no '
'earlier than 3 months after the release. '
'You should pass a QuantumCircuit instead, see '
'qiskit.circuit.library.QFT and the .inverse() method.',
DeprecationWarning, stacklevel=2)
self._ne_qfts = ne_qfts
self._circuit = None
self._output_register = None
self._input_register = None
self._init_constants()
def _init_constants(self):
# estimate evolution time
if self._evo_time is None:
lmax = sum([abs(p[0]) for p in self._operator.paulis])
if not self._negative_evals:
self._evo_time = (1 - 2 ** -self._num_ancillae) * 2 * np.pi / lmax
else:
self._evo_time = (1 / 2 - 2 ** -self._num_ancillae) * 2 * np.pi / lmax
# check for identify paulis to get its coef for applying global
# phase shift on ancillae later
num_identities = 0
for p in self._operator.paulis:
if np.all(p[1].z == 0) and np.all(p[1].x == 0):
num_identities += 1
if num_identities > 1:
raise RuntimeError('Multiple identity pauli terms are present.')
self._ancilla_phase_coef = p[0].real if isinstance(p[0], complex) else p[0]
[docs] def get_register_sizes(self):
return self._operator.num_qubits, self._num_ancillae
[docs] def get_scaling(self):
return self._evo_time
[docs] def construct_circuit(self, mode, register=None):
"""Construct the eigenvalues estimation using the PhaseEstimationCircuit
Args:
mode (str): construction mode, 'matrix' not supported
register (QuantumRegister): the register to use for the quantum state
Returns:
QuantumCircuit: object for the constructed circuit
Raises:
ValueError: QPE is only possible as a circuit not as a matrix
"""
if mode == 'matrix':
raise ValueError('QPE is only possible as a circuit not as a matrix.')
pe = PhaseEstimationCircuit(
operator=self._operator, state_in=None, iqft=self._iqft,
num_time_slices=self._num_time_slices, num_ancillae=self._num_ancillae,
expansion_mode=self._expansion_mode, expansion_order=self._expansion_order,
evo_time=self._evo_time
)
a = QuantumRegister(self._num_ancillae)
q = register
qc = pe.construct_circuit(state_register=q, ancillary_register=a)
# handle negative eigenvalues
if self._negative_evals:
self._handle_negative_evals(qc, a)
self._circuit = qc
self._output_register = a
self._input_register = q
return self._circuit
def _handle_negative_evals(self, qc, q):
sgn = q[0]
qs = [q[i] for i in range(1, len(q))]
def apply_ne_qft(ne_qft):
if isinstance(ne_qft, QuantumCircuit):
# check if QFT has the right size
if ne_qft.num_qubits != len(qs):
try: # try resizing
ne_qft.num_qubits = len(qs)
except AttributeError:
raise ValueError('The IQFT cannot be resized and does not have the '
'required size of {}'.format(len(qs)))
if hasattr(ne_qft, 'do_swaps'):
ne_qft.do_swaps = False
qc.append(ne_qft.to_instruction(), qs)
else:
ne_qft.construct_circuit(mode='circuit', qubits=qs, circuit=qc, do_swaps=False)
for qi in qs:
qc.cx(sgn, qi)
apply_ne_qft(self._ne_qfts[0])
for i, qi in enumerate(reversed(qs)):
qc.cu1(2 * np.pi / 2 ** (i + 1), sgn, qi)
apply_ne_qft(self._ne_qfts[1])