From c909f02e0694461fa4d77bc122f8868fdb0cfdb8 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 14 Jan 2021 18:36:20 -0500 Subject: [PATCH 01/37] Add PhaseEstimator and related classes --- qiskit/algorithms/__init__.py | 21 ++ .../algorithms/phase_estimators/__init__.py | 29 ++ .../hamiltonian_phase_estimation.py | 151 ++++++++++ .../hamiltonian_phase_estimation_result.py | 94 +++++++ .../phase_estimators/phase_estimation.py | 212 ++++++++++++++ .../phase_estimation_result.py | 147 ++++++++++ .../phase_estimation_scale.py | 137 +++++++++ .../phase_estimators/phase_estimator.py | 92 +++++++ .../python/algorithms/test_phase_estimator.py | 259 ++++++++++++++++++ 9 files changed, 1142 insertions(+) create mode 100644 qiskit/algorithms/phase_estimators/__init__.py create mode 100644 qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py create mode 100644 qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py create mode 100644 qiskit/algorithms/phase_estimators/phase_estimation.py create mode 100644 qiskit/algorithms/phase_estimators/phase_estimation_result.py create mode 100644 qiskit/algorithms/phase_estimators/phase_estimation_scale.py create mode 100644 qiskit/algorithms/phase_estimators/phase_estimator.py create mode 100644 test/python/algorithms/test_phase_estimator.py diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index d0efcbe5b005..471784d0567c 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -97,6 +97,20 @@ QAOA VQE +Phase Estimators +++++++++++++++++++++ +Algorithms that estimate the phases of eigenstates of a unitary. + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + HamiltonianPhaseEstimation + HamiltonianPhaseEstimationResult + PhaseEstimationScale + PhaseEstimation + PhaseEstimationResult + Exceptions ========== @@ -114,6 +128,8 @@ from .minimum_eigen_solvers import (VQE, VQEResult, QAOA, NumPyMinimumEigensolver, MinimumEigensolver, MinimumEigensolverResult) +from .phase_estimators import (HamiltonianPhaseEstimation, HamiltonianPhaseEstimationResult, PhaseEstimationScale, + PhaseEstimation, PhaseEstimationResult) from .exceptions import AlgorithmError __all__ = [ @@ -133,5 +149,10 @@ 'NumPyMinimumEigensolver', 'MinimumEigensolver', 'MinimumEigensolverResult', + 'HamiltonianPhaseEstimation', + 'HamiltonianPhaseEstimationResult', + 'PhaseEstimationScale', + 'PhaseEstimation', + 'PhaseEstimationResult', 'AlgorithmError', ] diff --git a/qiskit/algorithms/phase_estimators/__init__.py b/qiskit/algorithms/phase_estimators/__init__.py new file mode 100644 index 000000000000..19567798d9af --- /dev/null +++ b/qiskit/algorithms/phase_estimators/__init__.py @@ -0,0 +1,29 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""Phase Estimators.""" + +from .phase_estimator import PhaseEstimator +from .phase_estimation import PhaseEstimation +from .phase_estimation_result import PhaseEstimationResult +from .phase_estimation_scale import PhaseEstimationScale +from .hamiltonian_phase_estimation import HamiltonianPhaseEstimation +from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult + +__all__ = [ + 'PhaseEstimator', + 'PhaseEstimation', + 'PhaseEstimationResult', + 'PhaseEstimationScale', + 'HamiltonianPhaseEstimation', + 'HamiltonianPhaseEstimationResult' +] diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py new file mode 100644 index 000000000000..d10457a0e904 --- /dev/null +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -0,0 +1,151 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""Phase estimation for the spectrum of a Hamiltonian""" + +from typing import Optional, Union +from qiskit import QuantumCircuit +from qiskit.utils import QuantumInstance +from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp +from qiskit.providers import BaseBackend, Backend +from .phase_estimation import PhaseEstimation +from . import phase_estimation_scale +from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult +from .phase_estimation_scale import PhaseEstimationScale + + +class HamiltonianPhaseEstimation(PhaseEstimation): + r"""Run the Quantum Phase Estimation algorithm to find the eigenvalues of a Hermitian operator. + + This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimator`, differing only + in that the input in that class is a unitary operator, whereas here the input is a Hermitian + operator from which a unitary will be obtained by scaling and exponentiating. The scaling is + performed in order to prevent the phases from wrapping around :math:`2\pi`. This class uses and + works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to manage scaling the + Hamiltonian and the phases that are obtained by the QPE algorithm. This includes setting, or + computing, a bound on the eigenvalues of the operator, using this bound to obtain a scale + factor, scaling the operator, and shifting and scaling the measured phases to recover the + eigenvalues. + + Note that, although we speak of "evolving" the state according the the Hamiltonian, in the + present algorithm, we are not actually considering time evolution. Rather, the role of time is + played by the scaling factor, which is chosen to best extract the eigenvalues of the + Hamiltonian. + + A few of the ideas in the algorithm may be found in Ref. [1]. + + **Reference:** + + [1]: Quantum phase estimation of multiple eigenvalues for small-scale (noisy) experiments + T.E. O'Brien, B. Tarasinski, B.M. Terhal + https://arxiv.org/abs/1809.09697 + """ + def __init__(self, + num_evaluation_qubits: int, + hamiltonian: OperatorBase, + evolution: EvolutionBase, + state_preparation: Optional[QuantumCircuit] = None, + bound: Optional[float] = None, + quantum_instance: Optional[Union[QuantumInstance, + BaseBackend, Backend]] = None) -> None: + """ + Args: + num_evaluation_qubits: The number of qubits used in estimating the phase. The + phase will be estimated as a binary string with this many + bits. + hamiltonian: a Hamiltonian or Hermitian operator + evolution: An evolution object that generates a unitary from `hamiltonian`. + 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. + bound: An upper bound on the absolute value of the eigenvalues of + `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case + then a bound will be computed. + quantum_instance: The quantum instance on which the circuit will be run. + + Raises: + ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a + `SummedOp` whose terms are `PauliOp`s.) + """ + + + self._evolution = evolution + self._bound = bound + + # The term propto the identity is removed from hamiltonian. + # The coefficient of this term will be added to the eigenvalues. + id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) + self._hamiltonian = hamiltonian_no_id + self._id_coefficient = id_coefficient + + self._set_scale() + unitary = self._get_unitary() + + super().__init__(num_evaluation_qubits, + unitary=unitary, + pe_circuit=None, + num_unitary_qubits=None, + state_preparation=state_preparation, + quantum_instance=quantum_instance) + + def _set_scale(self) -> None: + if self._bound is None: + pe_scale = phase_estimation_scale.from_pauli_sum(self._hamiltonian) + self._pe_scale = pe_scale + else: + self._pe_scale = PhaseEstimationScale(self._bound) + + def _get_unitary(self) -> QuantumCircuit: + """Evolve the Hamiltonian to obtain a unitary. + + Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound + and compute the unitary by applying the evolution object. + """ + # scale so that phase does not wrap. + scaled_hamiltonian = -self._pe_scale.scale * self._hamiltonian + unitary = self._evolution.convert(scaled_hamiltonian.exp_i()) + if not isinstance(unitary, QuantumCircuit): + unitary_circuit = unitary.to_circuit() + else: + unitary_circuit = unitary + + # Decomposing twice allows some 1Q Hamiltonians to give correct results + # when using MatrixEvolution(), that otherwise would give incorrect results. + # It does not break any others that we tested. + return unitary_circuit.decompose().decompose() + + def _run(self) -> HamiltonianPhaseEstimationResult: + """Run the circuit and return and return `HamiltonianPhaseEstimationResult`. + """ + + circuit_result = self._quantum_instance.execute(self.construct_circuit()) + phases = self._compute_phases(circuit_result) + return HamiltonianPhaseEstimationResult( + self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, + circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) + + +def _remove_identity(pauli_sum): + """Remove any identity operators from `pauli_sum`. Return + the sum of the coefficients of the identities and the new operator. + """ + idcoeff = 0.0 + ops = [] + for op in pauli_sum: + p = op.primitive + if p.x.any() or p.z.any(): + ops.append(op) + else: + idcoeff += op.coeff + + return idcoeff, SummedOp(ops) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py new file mode 100644 index 000000000000..6064c2884df5 --- /dev/null +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -0,0 +1,94 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""Result from running HamiltonianPhaseEstimation""" + + +from typing import Dict, Union, cast +import numpy +from qiskit.result import Result +from .phase_estimation_result import PhaseEstimationResult +from .phase_estimation_scale import PhaseEstimationScale + + +class HamiltonianPhaseEstimationResult(PhaseEstimationResult): + """Store and manipulate results from running `HamiltonianPhaseEstimation`. + + This API of this class is nearly the same as `PhaseEstimatorResult`, differing only in + the presence of an additional keyword argument in the methods. If `scaled` + is `False`, then the phases are not translated and scaled to recover the + eigenvalues of the Hamiltonian. Instead `phi` in :math:`[0, 1)` is returned, + as is the case when then unitary is not derived from a Hamiltonian. + """ + def __init__(self, num_evaluation_qubits: int, circuit_result: Result, + phase_estimation_scale: PhaseEstimationScale, + id_coefficient: float, + phases: Union[numpy.ndarray, Dict[str, float]]) -> None: + """ + Args: + num_evaluation_qubits: number of qubits in phase-readout register. + circuit_result: result object returned by method running circuit. + phase_estimation_scale: object used to scale phases to obtain eigenvalues. + id_coefficient: The coefficient of the identity term in the Hamiltonian. + Eigenvalues are computed without this term so that the + coefficient must added to give correct eigenvalues. + This is done automatically when retrieving eigenvalues. + phases: ndarray or dict of phases and frequencies determined by QPE. + """ + self._phase_estimation_scale = phase_estimation_scale + self._id_coefficient = id_coefficient + + super().__init__(num_evaluation_qubits, circuit_result, phases) + + # pylint: disable=arguments-differ + def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, # type: ignore + as_float: bool = True) -> Dict[Union[str, float], float]: + """Filter phases as does `PhaseEstimatorResult.filter_phases`, with + the addition that `phi` is shifted and translated to return eigenvalues + of the Hamiltonian. + + Args: + cutoff: Minimum weight of number of counts required to keep a bit string. + The default value is `0.0`. + scaled: If False, return `phi` in :math:`[0, 1)` rather than the eigenvalues of + the Hamiltonian. + as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` + returned keys are bit strings. + + Raises: + ValueError: if as_float` is `False` and `scaled` is `True`. + + Returns: + A dict of filtered phases. + """ + if scaled and not as_float: + raise ValueError('`as_float` must be `True` if `scaled` is `True`.') + + phases = super().filter_phases(cutoff, as_float=as_float) + if scaled: + return cast(Dict, self._phase_estimation_scale.scale_phases(phases, + self._id_coefficient)) + else: + return cast(Dict, phases) + + @property + def most_likely_eigenvalue(self) -> float: + """The most likely eigenvalue of the Hamiltonian. + + This method calls `most_likely_phase` and scales the result to + obtain an eigenvalue. + + Returns: + The most likely eigenvalue of the Hamiltonian. + """ + phase = super().most_likely_phase + return self._phase_estimation_scale.scale_phase(phase, self._id_coefficient) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py new file mode 100644 index 000000000000..d346db80358a --- /dev/null +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -0,0 +1,212 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + + +"""The Quantum Phase Estimation Algorithm.""" + + +from typing import Optional, Union +import numpy +from qiskit.circuit import QuantumCircuit +import qiskit +import qiskit.circuit as circuit +from qiskit.circuit.classicalregister import ClassicalRegister +from qiskit.providers import BaseBackend +from qiskit.utils import QuantumInstance +from qiskit.result import Result +from .phase_estimation_result import PhaseEstimationResult, _sort_phases +from .phase_estimator import PhaseEstimator + + +class PhaseEstimation(PhaseEstimator): + """Run the Quantum Phase Estimation (QPE) algorithm. + + This runs a version of QPE with a multi-qubit register for reading the phase [1]. The main + inputs are the number of qubits in the phase-reading register, a state preparation circuit to + prepare an input state, and either + 1) A unitary that will act on the the input state, or + 2) A quantum-phase-estimation circuit in which the unitary is already embedded. + In case 1), an instance of `qiskit.circuit.PhaseEstimation`, a QPE circuit, containing the input + unitary will be constructed. After construction, the QPE circuit is run on a backend via the + `run` method, and the frequencies or counts of the phases represented by bitstrings are + recorded. The results are returned as an instance of + :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. + + If the input state is an eigenstate of the unitary, then in the ideal case, all probability is + concentrated on the bitstring corresponding to the eigenvalue of the input state. If the input + state is a superposition of eigenstates, then each bitstring is measured with a probability + corresponding to its weight in the superposition. In addition, if the phase is not representable + exactly by the phase-reading register, the probability will be spread across bitstrings, with an + amplitude that decreases with distance from the bitstring most closely approximating the phase. + + **Reference:** + + [1]: Michael A. Nielsen and Isaac L. Chuang. 2011. + Quantum Computation and Quantum Information: 10th Anniversary Edition (10th ed.). + Cambridge University Press, New York, NY, USA. + + """ + + def __init__(self, + num_evaluation_qubits: int, + unitary: Optional[QuantumCircuit] = None, + state_preparation: Optional[QuantumCircuit] = None, + pe_circuit: Optional[QuantumCircuit] = None, + num_unitary_qubits: Optional[int] = None, + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: + + """Args: + num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will + be estimated as a binary string with this many bits. + unitary: The circuit representing the unitary operator whose eigenvalues (via phase) + will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. + 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. + pe_circuit: The phase estimation circuit. + num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit` + if `pe_circuit` is passed. This parameter will be set from `unitary` + if `unitary` is passed. + quantum_instance: The quantum instance on which the circuit will be run. + + Raises: + ValueError: unless only one of `unitary` and `pe_circuit` is `None`. + `num_unitary_qubits` disagrees with size of `unitary`. + """ + + # Determine if user passed a unitary, or the entire QPE circuit with the unitary + # already embedded, and set properties. + + if num_evaluation_qubits is not None: + self._num_evaluation_qubits = num_evaluation_qubits + + if unitary is not None: + if pe_circuit is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._num_unitary_qubits = unitary.num_qubits + self._pe_circuit = circuit.library.PhaseEstimation(num_evaluation_qubits, unitary) + self._measurements_added = False + + if pe_circuit is not None: + if unitary is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._pe_circuit = pe_circuit + self._measurements_added = False + + if num_unitary_qubits is not None: + self._num_unitary_qubits = num_unitary_qubits + + if state_preparation is not None: + self._pe_circuit = self._pe_circuit.compose( + state_preparation, + qubits=range(self._num_evaluation_qubits, + self._num_evaluation_qubits + self._num_unitary_qubits), + front=True) + + self._quantum_instance = quantum_instance + + + def _add_classical_register(self) -> None: + """Add measurement instructions only if we are not using a state vector simulator.""" + if not self._quantum_instance.is_statevector and not self._measurements_added: + # Measure only the evaluation qubits. + regname = 'meas' + circ = self._pe_circuit + creg = ClassicalRegister(self._num_evaluation_qubits, regname) + circ.add_register(creg) + circ.barrier() + circ.measure(range(self._num_evaluation_qubits), range(self._num_evaluation_qubits)) + self._measurements_added = True + + def construct_circuit(self) -> QuantumCircuit: + """Return the circuit to be executed to estimate phases. + + This circuit includes as sub-circuits the core phase estimation circuit, + with the addition of the state-preparation circuit and possibly measurement instructions. + """ + self._add_classical_register() + return self._pe_circuit + + def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, + qiskit.result.Counts]: + """Compute frequencies/counts of phases from the result of running the QPE circuit. + + How the frequencies are computed depends on whether the backend computes amplitude or + samples outcomes. + + 1) If the backend is a statevector simulator, then the reduced density matrix of the + phase-reading register is computed from the combined phase-reading- and input-state + registers. The elements of the diagonal :math:`(i, i)` give the probability to measure the + each of the states `i`. The index `i` expressed as a binary integer with the LSB rightmost + gives the state of the phase-reading register with the LSB leftmost when interpreted as a + phase. In order to maintain the compact representation, the phases are maintained as decimal + integers. They may be converted to other forms via the results object, + `PhaseEstimationResult` or `HamiltonianPhaseEstimationResult`. + + 2) If the backend samples bitstrings, then the counts are first retrieved as a dict. The + binary strings (the keys) are then reversed so that the LSB is rightmost and the counts are + converted to frequencies. Then the keys are sorted according to increasing phase, so that + they can be easily understood when displaying or plotting a histogram. + + Args: + circuit_result: the result object returned by the backend that ran the QPE circuit. + + Returns: + Either a dict or numpy.ndarray representing the frequencies of the phases. + + """ + if self._quantum_instance.is_statevector: + state_vec = circuit_result.get_statevector() + evaluation_density_matrix = qiskit.quantum_info.partial_trace( + state_vec, + range(self._num_evaluation_qubits, + self._num_evaluation_qubits + self._num_unitary_qubits) + ) + phases = evaluation_density_matrix.probabilities() + else: + # return counts with keys sorted numerically + num_shots = circuit_result.results[0].shots + counts = circuit_result.get_counts() + phases = {k[::-1]: counts[k] / num_shots for k in counts.keys()} + phases = _sort_phases(phases) + phases = qiskit.result.Counts(phases, memory_slots=counts.memory_slots, + creg_sizes=counts.creg_sizes) + + return phases + + def estimate(self, + num_evaluation_qubits: Optional[int] = None, + unitary: Optional[QuantumCircuit] = None, + state_preparation: Optional[QuantumCircuit] = None, + pe_circuit: Optional[QuantumCircuit] = None, + num_unitary_qubits: Optional[int] = None) -> PhaseEstimationResult: + + super().estimate(num_evaluation_qubits, unitary, state_preparation, + pe_circuit, num_unitary_qubits) + return self._run() + + def _run(self) -> PhaseEstimationResult: + """Run the circuit and return and return `PhaseEstimationResult`. + + + Returns: + An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. + """ + + if hasattr(self._quantum_instance, 'execute'): + circuit_result = self._quantum_instance.execute(self.construct_circuit()) + else: + circuit_result = self._quantum_instance.run(self.construct_circuit()) + phases = self._compute_phases(circuit_result) + return PhaseEstimationResult(self._num_evaluation_qubits, circuit_result=circuit_result, + phases=phases) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py new file mode 100644 index 000000000000..8d760e22f7ce --- /dev/null +++ b/qiskit/algorithms/phase_estimators/phase_estimation_result.py @@ -0,0 +1,147 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""Result of running PhaseEstimation""" + +from typing import Dict, Union +import numpy +from qiskit.result import Result +from .phase_estimator import PhaseEstimatorResult + + +class PhaseEstimationResult(PhaseEstimatorResult): + """Store and manipulate results from running `PhaseEstimation`. + + This class is instantiated by the `PhaseEstimation` class, not via user code. + The `PhaseEstimation` class generates a list of phases and corresponding weights. Upon + completion it returns the results as an instance of this class. The main method for + accessing the results is `filter_phases`. + """ + + def __init__(self, num_evaluation_qubits: int, + circuit_result: Result, + phases: Union[numpy.ndarray, Dict[str, float]]) -> None: + """ + Args: + num_evaluation_qubits: number of qubits in phase-readout register. + circuit_result: result object returned by method running circuit. + phases: ndarray or dict of phases and frequencies determined by QPE. + """ + # int: number of qubits in phase-readout register + self._num_evaluation_qubits = num_evaluation_qubits + + self._phases = phases + self._circuit_result = circuit_result +# super().__init__({'phases': phases, 'circuit_result': circuit_result}) + + @property + def circuit_result(self) -> Result: + """Return the result object returned by running the QPE circuit (on hardware or simulator). + + This is useful for inspecting and troubleshooting the QPE algorithm. + """ + return self._circuit_result +# return self.get('circuit_result') + + @property + def most_likely_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. + """ + if isinstance(self.phases, dict): + binary_phase_string = max(self.phases, key=self.phases.get) + else: + # numpy.argmax ignores complex part of number. But, we take abs anyway + idx = numpy.argmax(abs(self.phases)) + binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] + phase = _bit_string_to_phase(binary_phase_string) + return phase + + def filter_phases(self, cutoff: float = 0.0, + as_float: bool = True) -> Dict: + """Return a filtered dict of phases (keys) and frequencies (values). + + Only phases with frequencies (counts) larger than `cutoff` are included. + It is assumed that the `run` method has been called so that the phases have been computed. + When using a noiseless, shot-based simulator to read a single phase that can + be represented exactly by `num_evaluation_qubits`, all the weight will + be concentrated on a single phase. In all other cases, many, or all, bit + strings will have non-zero weight. This method is useful for filtering + out these uninteresting bit strings. + + Args: + cutoff: Minimum weight of number of counts required to keep a bit string. + The default value is `0.0`. + as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` + returned keys are bit strings. + + Returns: + A filtered dict of phases (keys) and frequencies (values). + """ + if isinstance(self.phases, dict): + counts = self.phases + if as_float: + phases = {_bit_string_to_phase(k): counts[k] + for k in counts.keys() if counts[k] > cutoff} + else: + phases = {k: counts[k] for k in counts.keys() if counts[k] > cutoff} # type: ignore + + else: + phases = {} + for idx, amplitude in enumerate(self.phases): + if amplitude > cutoff: + # Each index corresponds to a computational basis state with the LSB rightmost. + # But, we chose to apply the unitaries such that the phase is recorded + # in reverse order. So, we reverse the bitstrings here. + binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] + if as_float: + _key = _bit_string_to_phase(binary_phase_string) + else: + _key = binary_phase_string + phases[_key] = amplitude + + phases = _sort_phases(phases) + + return phases + + +def _bit_string_to_phase(binary_string: str) -> float: + """Convert bit string to a normalized phase in :math:`[0,1)`. + + It is assumed that the bit string is correctly padded and that the order of + the bits has been reversed relative to their order when the counts + were recorded. The LSB is the right most when interpreting the bitstring as + a phase. + + Args: + binary_string: A string of characters '0' and '1'. + + Returns: + a phase scaled to :math:`[0,1)`. + """ + n_qubits = len(binary_string) + return int(binary_string, 2) / (2 ** n_qubits) + + +def _sort_phases(phases: Dict) -> Dict: + """Sort a dict of bit strings representing phases (keys) and frequencies (values) by bit string. + + The bit strings are sorted according to increasing phase. This relies on Python + preserving insertion order when building dicts. + """ + pkeys = list(phases.keys()) + pkeys.sort(reverse=False) # Sorts in order of the integer encoded by binary string + phases = {k: phases[k] for k in pkeys} + return phases diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py new file mode 100644 index 000000000000..4d50305ca1b8 --- /dev/null +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -0,0 +1,137 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""Scaling for Hamiltonian and eigenvalues to avoid phase wrapping""" + +from typing import Union, Dict, List +import numpy +from qiskit.opflow import SummedOp + + +class PhaseEstimationScale(): + """Set and use a bound on eigenvalues of a Hermitian operator in order to ensure phases are in + the desired range and to convert measured phases into eigenvectors. + + The `bound` is set when constructing this class. Then the method `scale` is used to find the + factor by which to scale the operator. + + If `bound` is equal exactly to the largest eigenvalue, and the smallest eigenvalue is minus the + largest, then these two eigenvalues will not be distinguished. For example, if the Hermitian + operator is the Pauli `Z operator with eigenvalues `+1` and `-1`, and `bound` is `1`, then both + eigenvalues will be mapped to `1`. This can be avoided by making `bound` a bit larger. + + Increasing `bound` decreases the part of the interval :math:`[0, 1)` that is used to map + eigenvalues to `phi`. However, sometimes this results in a better determination of the + eigenvalues, because 1) although there are fewer discrete phases in the useful range, it may + shift one of the discrete phases closer to the actual phase. And, 2) If one of the discrete + phases is close to, or exactly equal to the actual phase, then artifacts (probability) in + neighboring phases will be reduced. This is important because the artifacts may be larger than + the probability in a phase representing another eigenvalue of interest whose corresponding + eigenstate has a relatively small weight in the input state. + + """ + + def __init__(self, bound: float) -> None: + """ + Args: + bound: an upper bound on the absolute value of the + eigenvalues of a Hermitian operator. (The operator is not needed here.) + """ + self._bound = bound + + @property + def scale(self) -> float: + r"""Return the scale factor by which a Hermitian operator must be multiplied + so that the phase of the corresponding unitary is restricted to :math:`[-\pi, \pi]`. + This factor is computed from the bound on the absolute values of the eigenvalues + of the operator. The methods `scale_phase` and `scale_phases` are used recover + the eigenvalues corresponding the original (unscaled) Hermitian operator. + """ + return numpy.pi / self._bound + + def scale_phase(self, phi: float, id_coefficient: float) -> float: + r"""Convert a phase into an eigenvalue. + + The input phase `phi` corresponds to the eigenvalue of a unitary obtained by + exponentiating a scaled Hermitian operator. Recall that the phase + is obtained from `phi` as :math:`2\pi\phi`. Furthermore, the Hermitian operator + was scaled so that `phi` is restricted to :math:`[-1/2, 1/2]`, corresponding to + phases in :math:`[-\pi, \pi]`. But the values of `phi` read from the phase-readout + register are in :math:`[0, 1)`. Any value of `phi` greater than `1/2` corresponds + to a raw phase of minus the complement with respect to `1`. After this possible + shift, the phase is scaled by the inverse of the factor by which the + Hermitian operator was scaled to recover the eigenvalue of the Hermitian + operator. + + Args: + phi: Normalized phase in :math:`[0, 1)` to be converted to an eigenvalue. + id_coefficient: All eigenvalues are shifted by this value. + + Returns: + An eigenvalue computed from the input phase. + """ + w = 2 * self._bound + if phi <= 0.5: + return phi * w + id_coefficient + else: + return (phi - 1) * w + id_coefficient + + # pylint: disable=unsubscriptable-object + def scale_phases(self, phases: Union[List, Dict], id_coefficient: float) -> Union[Dict, List]: + """Convert a list or dict of phases to eigenvalues. + + The values in the list, or keys in the dict, are values of `phi` and + are converted as described in the description of `scale_phase`. In case + `phases` is a dict, the values of the dict are passed unchanged. + + Args: + phases: a list or dict of values of `phi`. + id_coefficient: All eigenvalues are shifted by this value. + + Returns: + Eigenvalues computed from phases. + """ + w = 2 * self._bound + if isinstance(phases, list): + phases = [(x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient) \ + for x in phases] + else: + phases = {((x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient)) \ + : phases[x] for x in phases.keys()} + + return phases + + +def from_pauli_sum(pauli_sum: SummedOp) -> PhaseEstimationScale: + """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. + + It is assumed that the `SummedOp` `pauli_sum` is the sum of `PauliOp`s. The bound on + the absolute value of the eigenvalues of the sum is obtained as the sum of the + absolute values of the coefficients of the terms. This is the best bound available in + the generic case. A `PhaseEstimationScale` object is instantiated using this bound. + + Args: + pauli_sum: A `SummedOp` whose terms are `PauliOp`s. + + Raises: + ValueError: if `pauli_sum` is not a sum of Pauli operators.' + + Returns: + A `PhaseEstimationScale` object + """ + if pauli_sum.primitive_strings() != {'Pauli'}: + raise ValueError( + '`pauli_sum` must be a sum of Pauli operators. Got primitives {}.'.format( + pauli_sum.primitive_strings())) + + bound = sum([abs(pauli_sum.coeff * pauli.coeff) for pauli in pauli_sum]) + return PhaseEstimationScale(bound) diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py new file mode 100644 index 000000000000..e7a661c5af7f --- /dev/null +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -0,0 +1,92 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""The Phase Estimator interface""" + +from typing import Union, Optional +from abc import ABC, abstractmethod +import numpy +import qiskit.circuit as circuit +from qiskit.circuit import QuantumCircuit +from qiskit.algorithms import AlgorithmResult + +# pylint: disable=attribute-defined-outside-init + + +class PhaseEstimator(ABC): + """The Phase Estimator interface + + Algorithms that can compute a phase for a unitary operator and + initial state may implement this interface to allow different + algorithms to be used interchangeably. + """ + + @abstractmethod + def estimate(self, + num_evaluation_qubits: Optional[int], + unitary: Optional[QuantumCircuit] = None, + state_preparation: Optional[QuantumCircuit] = None, + pe_circuit: Optional[QuantumCircuit] = None, + num_unitary_qubits: Optional[int] = None) -> 'PhaseEstimatorResult': + """ + Estimate the phase. + """ + if num_evaluation_qubits is not None: + self._num_evaluation_qubits = num_evaluation_qubits + + if unitary is not None: + if pe_circuit is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._num_unitary_qubits = unitary.num_qubits + self._pe_circuit = circuit.library.PhaseEstimation(self._num_evaluation_qubits, unitary) + self._measurements_added = False + + if pe_circuit is not None: + if unitary is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._pe_circuit = pe_circuit + self._measurements_added = False + + if num_unitary_qubits is not None: + self._num_unitary_qubits = num_unitary_qubits + + if state_preparation is not None: + self._pe_circuit = self._pe_circuit.compose( + state_preparation, + qubits=range(self._num_evaluation_qubits, + self._num_evaluation_qubits + self._num_unitary_qubits), + front=True) + + return PhaseEstimatorResult() + + +class PhaseEstimatorResult(AlgorithmResult): + """Phase Estimator Result.""" + + @property + def phases(self) -> Union[numpy.ndarray, dict]: + """Return all phases and their frequencies computed by QPE. + + This is an array or dict whose values correspond to weights on bit strings. + """ + # pylint: disable=no-member + return self._phases + + @property + def most_likely_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. + """ + raise NotImplementedError() diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py new file mode 100644 index 000000000000..10d86e1422ab --- /dev/null +++ b/test/python/algorithms/test_phase_estimator.py @@ -0,0 +1,259 @@ +# 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. + +"""Test phase estimation""" + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np +from qiskit.algorithms.phase_estimators import PhaseEstimation, HamiltonianPhaseEstimation +from qiskit.opflow.evolutions import PauliTrotterEvolution, MatrixEvolution +import qiskit +from qiskit.opflow import (H, X, Y, Z, I) + + +class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): + """Tests for obtaining eigenvalues from phase estimation""" + + def setUp(self): + super().setUp() + self.hamiltonian_1 = ((0.5 * X) + Y + Z).to_pauli_op() + + def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qubits=6, + backend=qiskit.BasicAer.get_backend('statevector_simulator'), + evolution=MatrixEvolution()): + """Run HamiltonianPhaseEstimation and return result with all phases.""" + quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) + phase_est = HamiltonianPhaseEstimation( + num_evaluation_qubits=num_evaluation_qubits, + hamiltonian=hamiltonian, quantum_instance=quantum_instance, + state_preparation=state_preparation, evolution=evolution) + result = phase_est.estimate() + return result + + # pylint: disable=invalid-name + def test_pauli_sum_1(self): + """Two eigenvalues from Pauli sum with X, Z""" + a1 = 0.5 + a2 = 1.0 + hamiltonian = ((a1 * X) + (a2 * Z)).to_pauli_op() + state_preparation = H.to_circuit() + result = self.hamiltonian_pe(hamiltonian, state_preparation) + phase_dict = result.filter_phases(0.162, as_float=True) + phases = list(phase_dict.keys()) + self.assertAlmostEqual(phases[0], 1.125, delta=0.001) + self.assertAlmostEqual(phases[1], -1.125, delta=0.001) + evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) + phase_dict = result.filter_phases(0.162, as_float=True) + phases = list(phase_dict.keys()) + phases.sort() + with self.subTest('Use PauliTrotterEvolution, first phase'): + self.assertAlmostEqual(phases[0], -1.125, delta=0.001) + with self.subTest('Use PauliTrotterEvolution, second phase'): + self.assertAlmostEqual(phases[1], 1.125, delta=0.001) + + def test_pauli_sum_2(self): + """Two eigenvalues from Pauli sum with X, Y, Z""" + hamiltonian = self.hamiltonian_1 + state_preparation = None + result = self.hamiltonian_pe(hamiltonian, state_preparation) + phase_dict = result.filter_phases(0.1, as_float=True) + phases = list(phase_dict.keys()) + self.assertAlmostEqual(phases[0], 1.484, delta=0.001) + self.assertAlmostEqual(phases[1], -1.484, delta=0.001) + evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=3) + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) + phase_dict = result.filter_phases(0.1, as_float=True) + phases = list(phase_dict.keys()) + with self.subTest('Use PauliTrotterEvolution, first phase'): + self.assertAlmostEqual(phases[0], 1.484, delta=0.001) + with self.subTest('Use PauliTrotterEvolution, second phase'): + self.assertAlmostEqual(phases[1], -1.484, delta=0.001) + + def test_water_hamiltonian(self): + """Test water hamiltonian""" + hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I^Z)) \ + + (-0.3979374248431802 * (Z^I)) + (-0.011280104256235324 * (Z^Z)) \ + + (0.18093119978423147 * (X^X)) + state_preparation = (I^H).to_circuit() + evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) + result = self.hamiltonian_pe(hamiltonian.to_pauli_op(), state_preparation, evolution=evo) + with self.subTest('Most likely eigenvalues'): + self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=.001) + with self.subTest('All eigenvalues'): + phase_dict = result.filter_phases(0.1) + phases = list(phase_dict.keys()) + self.assertAlmostEqual(phases[0], -0.8979, delta=0.001) + self.assertAlmostEqual(phases[1], -1.8551, delta=0.001) + self.assertAlmostEqual(phases[2], -1.2376, delta=0.001) + + def test_matrix_evolution(self): + """1Q Hamiltonian with MatrixEvolution""" + hamiltonian = ((0.5 * X) + (0.6 * Y) + (0.7 * I)).to_pauli_op() + state_preparation = None + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=MatrixEvolution()) + phase_dict = result.filter_phases(0.2, as_float=True) + phases = list(phase_dict.keys()) + self.assertAlmostEqual(phases[0], 1.490, delta=0.001) + self.assertAlmostEqual(phases[1], -0.090, delta=0.001) + + def _setup_from_bound(self, evolution): + hamiltonian = self.hamiltonian_1 + state_preparation = None + bound = 1.2 * sum([abs(hamiltonian.coeff * pauli.coeff) for pauli in hamiltonian]) + backend = qiskit.BasicAer.get_backend('statevector_simulator') + qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) + phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, + hamiltonian=hamiltonian, + bound=bound, + quantum_instance=qi, + state_preparation=state_preparation, + evolution=evolution) + result = phase_est.estimate() + return result + + def test_from_bound(self): + """HamiltonianPhaseEstimation with bound""" + result = self._setup_from_bound(MatrixEvolution()) + phases = result.filter_phases() + with self.subTest('test phases has the correct length'): + self.assertEqual(len(phases), 2) + with self.subTest('test scaled phases are correct'): + self.assertEqual(list(phases.keys()), [1.5, -1.5]) + phases = result.filter_phases(scaled=False) + with self.subTest('test unscaled phases are correct'): + self.assertEqual(list(phases.keys()), [0.25, 0.75]) + with self.subTest('test most_likely_phase method'): + self.assertEqual(result.most_likely_eigenvalue, 1.5) + self.assertEqual(result.most_likely_phase, 0.25) + + def test_trotter_from_bound(self): + """HamiltonianPhaseEstimation with bound and Trotterization""" + result = self._setup_from_bound(PauliTrotterEvolution(trotter_mode='suzuki', reps=3)) + phase_dict = result.filter_phases(0.1) + phases = list(phase_dict.keys()) + with self.subTest('test phases has the correct length'): + self.assertEqual(len(phases), 2) + with self.subTest('test phases has correct values'): + self.assertAlmostEqual(phases[0], 1.5, delta=0.001) + self.assertAlmostEqual(phases[1], -1.5, delta=0.001) + + +class TestPhaseEstimation(QiskitAlgorithmsTestCase): + """Evolution tests.""" + + # pylint: disable=invalid-name + def one_phase(self, unitary_circuit, state_preparation=None, n_eval_qubits=6, + backend=qiskit.BasicAer.get_backend('qasm_simulator')): + """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, + `state_preparation`. Return the bit string with the largest amplitude. + """ + qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) + p_est = PhaseEstimation(num_evaluation_qubits=n_eval_qubits, + unitary=unitary_circuit, + quantum_instance=qi, + state_preparation=state_preparation) + result = p_est.estimate() + phase = result.most_likely_phase + return phase + + def test_qpe_Z0(self): + """eigenproblem Z, |0>""" + + unitary_circuit = Z.to_circuit() + state_preparation = None # prepare |0> + phase = self.one_phase(unitary_circuit, state_preparation) + self.assertEqual(phase, 0.0) + + def test_qpe_Z0_statevector(self): + """eigenproblem Z, |0>, statevector simulator""" + + unitary_circuit = Z.to_circuit() + state_preparation = None # prepare |0> + phase = self.one_phase(unitary_circuit, state_preparation, + backend=qiskit.BasicAer.get_backend('statevector_simulator')) + self.assertEqual(phase, 0.0) + + def test_qpe_Z1(self): + """eigenproblem Z, |1>""" + unitary_circuit = Z.to_circuit() + state_preparation = X.to_circuit() # prepare |1> + phase = self.one_phase(unitary_circuit, state_preparation) + self.assertEqual(phase, 0.5) + + def test_qpe_Z1_estimate(self): + """eigenproblem Z, |1>, estimate interface""" + unitary_circuit = Z.to_circuit() + state_preparation = X.to_circuit() # prepare |1> + backend = qiskit.BasicAer.get_backend('statevector_simulator') + qi = qiskit.utils.QuantumInstance(backend=backend) + num_evaluation_qubits = 6 + pe = PhaseEstimation(num_evaluation_qubits, quantum_instance=qi) + result = pe.estimate(unitary=unitary_circuit, state_preparation=state_preparation) + phase = result.most_likely_phase + self.assertEqual(phase, 0.5) + + def test_qpe_Xplus(self): + """eigenproblem X, |+>""" + unitary_circuit = X.to_circuit() + state_preparation = H.to_circuit() # prepare |+> + phase = self.one_phase(unitary_circuit, state_preparation) + self.assertEqual(phase, 0.0) + + def test_qpe_Xminus(self): + """eigenproblem X, |->""" + unitary_circuit = X.to_circuit() + state_preparation = X.to_circuit() + state_preparation.append(H.to_circuit(), [0]) # prepare |-> + phase = self.one_phase(unitary_circuit, state_preparation) + self.assertEqual(phase, 0.5) + + def phase_estimation(self, unitary_circuit, state_preparation=None, num_evaluation_qubits=6, + backend=qiskit.BasicAer.get_backend('qasm_simulator')): + """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, + `state_preparation`. Return all results + """ + qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) + phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, + unitary=unitary_circuit, + quantum_instance=qi, + state_preparation=state_preparation) + result = phase_est.estimate() + return result + + def test_qpe_Zplus(self): + """superposition eigenproblem Z, |+>""" + unitary_circuit = Z.to_circuit() + state_preparation = H.to_circuit() # prepare |+> + result = self.phase_estimation( + unitary_circuit, state_preparation, + backend=qiskit.BasicAer.get_backend('statevector_simulator')) + phases = result.filter_phases(1e-15, as_float=True) + with self.subTest('test phases has correct values'): + self.assertEqual(list(phases.keys()), [0.0, 0.5]) + with self.subTest('test phases has correct probabilities'): + np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) + + def test_qpe_Zplus_strings(self): + """superposition eigenproblem Z, |+>, bitstrings""" + unitary_circuit = Z.to_circuit() + state_preparation = H.to_circuit() # prepare |+> + result = self.phase_estimation( + unitary_circuit, state_preparation, + backend=qiskit.BasicAer.get_backend('statevector_simulator')) + phases = result.filter_phases(1e-15, as_float=False) + self.assertEqual(list(phases.keys()), ['000000', '100000']) + + +if __name__ == '__main__': + unittest.main() From b32e75dd909bcdbe5e2bfe25d24d92305cd2a29a Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 20 Jan 2021 15:58:47 -0500 Subject: [PATCH 02/37] Fix linter complaints --- qiskit/algorithms/__init__.py | 4 ++-- .../phase_estimators/hamiltonian_phase_estimation.py | 1 - qiskit/algorithms/phase_estimators/phase_estimation.py | 1 - .../algorithms/phase_estimators/phase_estimation_scale.py | 6 +++--- test/python/algorithms/test_phase_estimator.py | 6 +++--- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index f31b7c0ef8a6..acd4a6b491f6 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -155,8 +155,8 @@ from .minimum_eigen_solvers import (VQE, VQEResult, QAOA, NumPyMinimumEigensolver, MinimumEigensolver, MinimumEigensolverResult) -from .phase_estimators import (HamiltonianPhaseEstimation, HamiltonianPhaseEstimationResult, PhaseEstimationScale, - PhaseEstimation, PhaseEstimationResult) +from .phase_estimators import (HamiltonianPhaseEstimation, HamiltonianPhaseEstimationResult, + PhaseEstimationScale, PhaseEstimation, PhaseEstimationResult) from .exceptions import AlgorithmError __all__ = [ diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index d10457a0e904..6f2b4a0fa332 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -78,7 +78,6 @@ def __init__(self, `SummedOp` whose terms are `PauliOp`s.) """ - self._evolution = evolution self._bound = bound diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index d346db80358a..c543799d5f6f 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -115,7 +115,6 @@ def __init__(self, self._quantum_instance = quantum_instance - def _add_classical_register(self) -> None: """Add measurement instructions only if we are not using a state vector simulator.""" if not self._quantum_instance.is_statevector and not self._measurements_added: diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 4d50305ca1b8..7b5d48f95f03 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -102,11 +102,11 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float) -> Unio """ w = 2 * self._bound if isinstance(phases, list): - phases = [(x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient) \ + phases = [(x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient) for x in phases] else: - phases = {((x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient)) \ - : phases[x] for x in phases.keys()} + phases = {((x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient)): + phases[x] for x in phases.keys()} return phases diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 10d86e1422ab..a9c60a9fb505 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -82,9 +82,9 @@ def test_pauli_sum_2(self): def test_water_hamiltonian(self): """Test water hamiltonian""" - hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I^Z)) \ - + (-0.3979374248431802 * (Z^I)) + (-0.011280104256235324 * (Z^Z)) \ - + (0.18093119978423147 * (X^X)) + hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I ^ Z)) \ + + (-0.3979374248431802 * (Z ^ I)) + (-0.011280104256235324 * (Z ^ Z)) \ + + (0.18093119978423147 * (X ^ X)) state_preparation = (I^H).to_circuit() evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) result = self.hamiltonian_pe(hamiltonian.to_pauli_op(), state_preparation, evolution=evo) From 913477345c5dac39585fea15b5939b68bd713250 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 11:31:23 -0500 Subject: [PATCH 03/37] Update qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py Co-authored-by: Julien Gacon --- .../algorithms/phase_estimators/hamiltonian_phase_estimation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 6f2b4a0fa332..3d9e55f73058 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -47,7 +47,7 @@ class HamiltonianPhaseEstimation(PhaseEstimation): [1]: Quantum phase estimation of multiple eigenvalues for small-scale (noisy) experiments T.E. O'Brien, B. Tarasinski, B.M. Terhal - https://arxiv.org/abs/1809.09697 + `arXiv:1809.09697 `_ """ def __init__(self, num_evaluation_qubits: int, From e5346b2b104bda98b472c375e72206dfb5e8970d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 12:02:59 -0500 Subject: [PATCH 04/37] Fix indentation in doc string --- .../phase_estimators/hamiltonian_phase_estimation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 3d9e55f73058..e455bda40af0 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -65,9 +65,8 @@ def __init__(self, hamiltonian: a Hamiltonian or Hermitian operator evolution: An evolution object that generates a unitary from `hamiltonian`. 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. + 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. bound: An upper bound on the absolute value of the eigenvalues of `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case then a bound will be computed. From 014db0ac9153695f0183a6110082c0dfc04312fc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 12:05:11 -0500 Subject: [PATCH 05/37] Fix more indentation in doc string --- .../phase_estimators/hamiltonian_phase_estimation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index e455bda40af0..69bff23c74d1 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -59,9 +59,8 @@ def __init__(self, BaseBackend, Backend]] = None) -> None: """ Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The - phase will be estimated as a binary string with this many - bits. + num_evaluation_qubits: The number of qubits used in estimating the phase. The phase + will be estimated as a binary string with this many bits. hamiltonian: a Hamiltonian or Hermitian operator evolution: An evolution object that generates a unitary from `hamiltonian`. state_preparation: The circuit that prepares the state whose eigenphase will be From 58690588157115b8a09917ecd79d86929491afd6 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 12:12:52 -0500 Subject: [PATCH 06/37] Comment on reasons for removing identity term from Hamiltonian --- .../phase_estimators/hamiltonian_phase_estimation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 69bff23c74d1..3797920a7d37 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -61,7 +61,7 @@ def __init__(self, Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. - hamiltonian: a Hamiltonian or Hermitian operator + hamiltonian: a Hermitian operator. evolution: An evolution object that generates a unitary from `hamiltonian`. 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 @@ -80,6 +80,12 @@ def __init__(self, self._bound = bound # The term propto the identity is removed from hamiltonian. + # This is done for three reasons: + # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some + # cases. + # 2. Allow working with a simpler Hamiltonian, one with fewer terms. + # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. + # occupies more of the range of values representable by the qubit register. # The coefficient of this term will be added to the eigenvalues. id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) self._hamiltonian = hamiltonian_no_id From 800fc8b3333f2c02c452cd9f150c92757c710698 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 12:24:40 -0500 Subject: [PATCH 07/37] Replace mistaken description "water" by "H2" --- test/python/algorithms/test_phase_estimator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index a9c60a9fb505..ddf0c04ce3bf 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -80,8 +80,8 @@ def test_pauli_sum_2(self): with self.subTest('Use PauliTrotterEvolution, second phase'): self.assertAlmostEqual(phases[1], -1.484, delta=0.001) - def test_water_hamiltonian(self): - """Test water hamiltonian""" + def test_H2_hamiltonian(self): + """Test H2 hamiltonian""" hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I ^ Z)) \ + (-0.3979374248431802 * (Z ^ I)) + (-0.011280104256235324 * (Z ^ Z)) \ + (0.18093119978423147 * (X ^ X)) From bd0d8d5b8450036e9cfeb94f3a480cf35c223baa Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 3 Feb 2021 21:29:15 -0500 Subject: [PATCH 08/37] Add default phase shift of 0.0 for identity term --- qiskit/algorithms/phase_estimators/phase_estimation_scale.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 7b5d48f95f03..7b152a709ae6 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -58,7 +58,7 @@ def scale(self) -> float: """ return numpy.pi / self._bound - def scale_phase(self, phi: float, id_coefficient: float) -> float: + def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: r"""Convert a phase into an eigenvalue. The input phase `phi` corresponds to the eigenvalue of a unitary obtained by @@ -86,7 +86,7 @@ def scale_phase(self, phi: float, id_coefficient: float) -> float: return (phi - 1) * w + id_coefficient # pylint: disable=unsubscriptable-object - def scale_phases(self, phases: Union[List, Dict], id_coefficient: float) -> Union[Dict, List]: + def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0) -> Union[Dict, List]: """Convert a list or dict of phases to eigenvalues. The values in the list, or keys in the dict, are values of `phi` and From 736e42bb8b7bbb5e878828cde81042a0b219dd25 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Fri, 5 Feb 2021 09:54:42 -0500 Subject: [PATCH 09/37] Remove function call from default arg Replace default arg with None, and then set the desired default in the body of the function. --- test/python/algorithms/test_phase_estimator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index ddf0c04ce3bf..56146c9d05a0 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -154,10 +154,12 @@ class TestPhaseEstimation(QiskitAlgorithmsTestCase): # pylint: disable=invalid-name def one_phase(self, unitary_circuit, state_preparation=None, n_eval_qubits=6, - backend=qiskit.BasicAer.get_backend('qasm_simulator')): + backend=None): """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, `state_preparation`. Return the bit string with the largest amplitude. """ + if backend is None: + backend = qiskit.BasicAer.get_backend('qasm_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) p_est = PhaseEstimation(num_evaluation_qubits=n_eval_qubits, unitary=unitary_circuit, From 303d4f76a2e06d5a5c67c659cfd7d560aaef28d2 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 23 Feb 2021 22:10:02 -0500 Subject: [PATCH 10/37] Refactor _run and rename to _result Move all common code out of _run for HamiltonianPhaseEstimation and PhaseEstimation, and rename _run to _result, because it only creates the result class. --- .../phase_estimators/hamiltonian_phase_estimation.py | 7 +------ .../algorithms/phase_estimators/phase_estimation.py | 12 +++++------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 3797920a7d37..33ca9a835363 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -127,12 +127,7 @@ def _get_unitary(self) -> QuantumCircuit: # It does not break any others that we tested. return unitary_circuit.decompose().decompose() - def _run(self) -> HamiltonianPhaseEstimationResult: - """Run the circuit and return and return `HamiltonianPhaseEstimationResult`. - """ - - circuit_result = self._quantum_instance.execute(self.construct_circuit()) - phases = self._compute_phases(circuit_result) + def _result(self, circuit_result, phases) -> HamiltonianPhaseEstimationResult: return HamiltonianPhaseEstimationResult( self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index c543799d5f6f..2065ce467cc6 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -189,23 +189,21 @@ def estimate(self, state_preparation: Optional[QuantumCircuit] = None, pe_circuit: Optional[QuantumCircuit] = None, num_unitary_qubits: Optional[int] = None) -> PhaseEstimationResult: - - super().estimate(num_evaluation_qubits, unitary, state_preparation, - pe_circuit, num_unitary_qubits) - return self._run() - - def _run(self) -> PhaseEstimationResult: """Run the circuit and return and return `PhaseEstimationResult`. Returns: An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. """ - + super().estimate(num_evaluation_qubits, unitary, state_preparation, + pe_circuit, num_unitary_qubits) if hasattr(self._quantum_instance, 'execute'): circuit_result = self._quantum_instance.execute(self.construct_circuit()) else: circuit_result = self._quantum_instance.run(self.construct_circuit()) phases = self._compute_phases(circuit_result) + return self._result(circuit_result, phases) + + def _result(self, circuit_result, phases) -> PhaseEstimationResult: return PhaseEstimationResult(self._num_evaluation_qubits, circuit_result=circuit_result, phases=phases) From 464e8adf50c3e206022111acc5a08b8dd9ed2d00 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 24 Feb 2021 16:46:47 -0500 Subject: [PATCH 11/37] Move some inputs and calculation to estimate method --- .../hamiltonian_phase_estimation.py | 85 ++++++++----------- .../phase_estimators/phase_estimation.py | 52 +++--------- .../phase_estimation_result.py | 2 - .../python/algorithms/test_phase_estimator.py | 24 +++--- 4 files changed, 59 insertions(+), 104 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 33ca9a835363..123623bbdb94 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -49,18 +49,44 @@ class HamiltonianPhaseEstimation(PhaseEstimation): T.E. O'Brien, B. Tarasinski, B.M. Terhal `arXiv:1809.09697 `_ """ - def __init__(self, - num_evaluation_qubits: int, - hamiltonian: OperatorBase, + + def _set_scale(self) -> None: + if self._bound is None: + pe_scale = phase_estimation_scale.from_pauli_sum(self._hamiltonian) + self._pe_scale = pe_scale + else: + self._pe_scale = PhaseEstimationScale(self._bound) + + def _get_unitary(self) -> QuantumCircuit: + """Evolve the Hamiltonian to obtain a unitary. + + Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound + and compute the unitary by applying the evolution object. + """ + # scale so that phase does not wrap. + scaled_hamiltonian = -self._pe_scale.scale * self._hamiltonian + unitary = self._evolution.convert(scaled_hamiltonian.exp_i()) + if not isinstance(unitary, QuantumCircuit): + unitary_circuit = unitary.to_circuit() + else: + unitary_circuit = unitary + + # Decomposing twice allows some 1Q Hamiltonians to give correct results + # when using MatrixEvolution(), that otherwise would give incorrect results. + # It does not break any others that we tested. + return unitary_circuit.decompose().decompose() + + def _result(self, circuit_result, phases) -> HamiltonianPhaseEstimationResult: + return HamiltonianPhaseEstimationResult( + self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, + circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) + + def estimate(self, hamiltonian: OperatorBase, evolution: EvolutionBase, state_preparation: Optional[QuantumCircuit] = None, - bound: Optional[float] = None, - quantum_instance: Optional[Union[QuantumInstance, - BaseBackend, Backend]] = None) -> None: + bound: Optional[float] = None): """ Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase - will be estimated as a binary string with this many bits. hamiltonian: a Hermitian operator. evolution: An evolution object that generates a unitary from `hamiltonian`. state_preparation: The circuit that prepares the state whose eigenphase will be @@ -69,7 +95,6 @@ def __init__(self, bound: An upper bound on the absolute value of the eigenvalues of `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case then a bound will be computed. - quantum_instance: The quantum instance on which the circuit will be run. Raises: ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a @@ -78,7 +103,6 @@ def __init__(self, self._evolution = evolution self._bound = bound - # The term propto the identity is removed from hamiltonian. # This is done for three reasons: # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some @@ -90,47 +114,10 @@ def __init__(self, id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) self._hamiltonian = hamiltonian_no_id self._id_coefficient = id_coefficient - self._set_scale() unitary = self._get_unitary() - - super().__init__(num_evaluation_qubits, - unitary=unitary, - pe_circuit=None, - num_unitary_qubits=None, - state_preparation=state_preparation, - quantum_instance=quantum_instance) - - def _set_scale(self) -> None: - if self._bound is None: - pe_scale = phase_estimation_scale.from_pauli_sum(self._hamiltonian) - self._pe_scale = pe_scale - else: - self._pe_scale = PhaseEstimationScale(self._bound) - - def _get_unitary(self) -> QuantumCircuit: - """Evolve the Hamiltonian to obtain a unitary. - - Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound - and compute the unitary by applying the evolution object. - """ - # scale so that phase does not wrap. - scaled_hamiltonian = -self._pe_scale.scale * self._hamiltonian - unitary = self._evolution.convert(scaled_hamiltonian.exp_i()) - if not isinstance(unitary, QuantumCircuit): - unitary_circuit = unitary.to_circuit() - else: - unitary_circuit = unitary - - # Decomposing twice allows some 1Q Hamiltonians to give correct results - # when using MatrixEvolution(), that otherwise would give incorrect results. - # It does not break any others that we tested. - return unitary_circuit.decompose().decompose() - - def _result(self, circuit_result, phases) -> HamiltonianPhaseEstimationResult: - return HamiltonianPhaseEstimationResult( - self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, - circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) + return super().estimate(unitary=unitary, + state_preparation=state_preparation) def _remove_identity(pauli_sum): diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 2065ce467cc6..8bc31d370e47 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -58,25 +58,11 @@ class PhaseEstimation(PhaseEstimator): def __init__(self, num_evaluation_qubits: int, - unitary: Optional[QuantumCircuit] = None, - state_preparation: Optional[QuantumCircuit] = None, - pe_circuit: Optional[QuantumCircuit] = None, - num_unitary_qubits: Optional[int] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: """Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. - unitary: The circuit representing the unitary operator whose eigenvalues (via phase) - will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. - 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. - pe_circuit: The phase estimation circuit. - num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit` - if `pe_circuit` is passed. This parameter will be set from `unitary` - if `unitary` is passed. quantum_instance: The quantum instance on which the circuit will be run. Raises: @@ -84,37 +70,12 @@ def __init__(self, `num_unitary_qubits` disagrees with size of `unitary`. """ - # Determine if user passed a unitary, or the entire QPE circuit with the unitary - # already embedded, and set properties. - if num_evaluation_qubits is not None: self._num_evaluation_qubits = num_evaluation_qubits - if unitary is not None: - if pe_circuit is not None: - raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._num_unitary_qubits = unitary.num_qubits - self._pe_circuit = circuit.library.PhaseEstimation(num_evaluation_qubits, unitary) - self._measurements_added = False - - if pe_circuit is not None: - if unitary is not None: - raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._pe_circuit = pe_circuit - self._measurements_added = False - - if num_unitary_qubits is not None: - self._num_unitary_qubits = num_unitary_qubits - - if state_preparation is not None: - self._pe_circuit = self._pe_circuit.compose( - state_preparation, - qubits=range(self._num_evaluation_qubits, - self._num_evaluation_qubits + self._num_unitary_qubits), - front=True) - self._quantum_instance = quantum_instance + def _add_classical_register(self) -> None: """Add measurement instructions only if we are not using a state vector simulator.""" if not self._quantum_instance.is_statevector and not self._measurements_added: @@ -191,6 +152,17 @@ def estimate(self, num_unitary_qubits: Optional[int] = None) -> PhaseEstimationResult: """Run the circuit and return and return `PhaseEstimationResult`. + Args: + unitary: The circuit representing the unitary operator whose eigenvalues (via phase) + will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. + 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. + pe_circuit: The phase estimation circuit. + num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit` + if `pe_circuit` is passed. This parameter will be set from `unitary` + if `unitary` is passed. Returns: An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py index 8d760e22f7ce..2a2f270c764d 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_result.py @@ -41,7 +41,6 @@ def __init__(self, num_evaluation_qubits: int, self._phases = phases self._circuit_result = circuit_result -# super().__init__({'phases': phases, 'circuit_result': circuit_result}) @property def circuit_result(self) -> Result: @@ -50,7 +49,6 @@ def circuit_result(self) -> Result: This is useful for inspecting and troubleshooting the QPE algorithm. """ return self._circuit_result -# return self.get('circuit_result') @property def most_likely_phase(self) -> float: diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 56146c9d05a0..e5918033da77 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -35,9 +35,10 @@ def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qub quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, - hamiltonian=hamiltonian, quantum_instance=quantum_instance, + quantum_instance=quantum_instance) + result = phase_est.estimate( + hamiltonian=hamiltonian, state_preparation=state_preparation, evolution=evolution) - result = phase_est.estimate() return result # pylint: disable=invalid-name @@ -114,12 +115,11 @@ def _setup_from_bound(self, evolution): backend = qiskit.BasicAer.get_backend('statevector_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, - hamiltonian=hamiltonian, + quantum_instance=qi) + result = phase_est.estimate(hamiltonian=hamiltonian, bound=bound, - quantum_instance=qi, - state_preparation=state_preparation, - evolution=evolution) - result = phase_est.estimate() + evolution=evolution, + state_preparation=state_preparation) return result def test_from_bound(self): @@ -162,10 +162,9 @@ def one_phase(self, unitary_circuit, state_preparation=None, n_eval_qubits=6, backend = qiskit.BasicAer.get_backend('qasm_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) p_est = PhaseEstimation(num_evaluation_qubits=n_eval_qubits, - unitary=unitary_circuit, - quantum_instance=qi, + quantum_instance=qi) + result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - result = p_est.estimate() phase = result.most_likely_phase return phase @@ -227,10 +226,9 @@ def phase_estimation(self, unitary_circuit, state_preparation=None, num_evaluati """ qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, - unitary=unitary_circuit, - quantum_instance=qi, + quantum_instance=qi) + result = phase_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - result = phase_est.estimate() return result def test_qpe_Zplus(self): From 8bada3de05b74ae3918ec5e474a73aa154418a71 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 24 Feb 2021 17:59:39 -0500 Subject: [PATCH 12/37] Add cutoff to filter in test_from_bound * An unknown change in the past three weeks caused tests to fail with the default filter threshold of 0 (no filtering). I added a small cutoff so that the tests pass again. * Fix linter complaints --- .../hamiltonian_phase_estimation.py | 20 +++++++++++++++++-- .../phase_estimators/phase_estimation.py | 4 +++- .../phase_estimation_scale.py | 3 ++- .../python/algorithms/test_phase_estimator.py | 5 +++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 123623bbdb94..15962cc6ee7a 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -16,7 +16,7 @@ from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp -from qiskit.providers import BaseBackend, Backend +from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from . import phase_estimation_scale from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult @@ -50,6 +50,17 @@ class HamiltonianPhaseEstimation(PhaseEstimation): `arXiv:1809.09697 `_ """ + def __init__(self, + num_evaluation_qubits: int, + quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: + self._pe_scale = None + self._bound = None + self._evolution = None + self._hamiltonian = None + self._id_coefficient = None + super().__init__(num_evaluation_qubits=num_evaluation_qubits, + quantum_instance=quantum_instance) + def _set_scale(self) -> None: if self._bound is None: pe_scale = phase_estimation_scale.from_pauli_sum(self._hamiltonian) @@ -81,10 +92,11 @@ def _result(self, circuit_result, phases) -> HamiltonianPhaseEstimationResult: self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) + # pylint: disable=arguments-differ def estimate(self, hamiltonian: OperatorBase, evolution: EvolutionBase, state_preparation: Optional[QuantumCircuit] = None, - bound: Optional[float] = None): + bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: """ Args: hamiltonian: a Hermitian operator. @@ -96,6 +108,10 @@ def estimate(self, hamiltonian: OperatorBase, `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case then a bound will be computed. + Returns: + HamiltonianPhaseEstimationResult instance containing the result of the estimation + and diagnostic information. + Raises: ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a `SummedOp` whose terms are `PauliOp`s.) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 8bc31d370e47..3a03d71da737 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -18,7 +18,6 @@ import numpy from qiskit.circuit import QuantumCircuit import qiskit -import qiskit.circuit as circuit from qiskit.circuit.classicalregister import ClassicalRegister from qiskit.providers import BaseBackend from qiskit.utils import QuantumInstance @@ -70,6 +69,7 @@ def __init__(self, `num_unitary_qubits` disagrees with size of `unitary`. """ + self._measurements_added = False if num_evaluation_qubits is not None: self._num_evaluation_qubits = num_evaluation_qubits @@ -153,6 +153,8 @@ def estimate(self, """Run the circuit and return and return `PhaseEstimationResult`. Args: + num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will + be estimated as a binary string with this many bits. unitary: The circuit representing the unitary operator whose eigenvalues (via phase) will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. state_preparation: The circuit that prepares the state whose eigenphase will be diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 7b152a709ae6..fd3505838d50 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -86,7 +86,8 @@ def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: return (phi - 1) * w + id_coefficient # pylint: disable=unsubscriptable-object - def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0) -> Union[Dict, List]: + def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 + ) -> Union[Dict, List]: """Convert a list or dict of phases to eigenvalues. The values in the list, or keys in the dict, are values of `phi` and diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index e5918033da77..5ac80c1290e6 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -125,12 +125,13 @@ def _setup_from_bound(self, evolution): def test_from_bound(self): """HamiltonianPhaseEstimation with bound""" result = self._setup_from_bound(MatrixEvolution()) - phases = result.filter_phases() + cutoff = 0.01 + phases = result.filter_phases(cutoff) with self.subTest('test phases has the correct length'): self.assertEqual(len(phases), 2) with self.subTest('test scaled phases are correct'): self.assertEqual(list(phases.keys()), [1.5, -1.5]) - phases = result.filter_phases(scaled=False) + phases = result.filter_phases(cutoff, scaled=False) with self.subTest('test unscaled phases are correct'): self.assertEqual(list(phases.keys()), [0.25, 0.75]) with self.subTest('test most_likely_phase method'): From 3af7237a68c970a486aebdba095d21b9de86876d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 24 Feb 2021 20:41:07 -0500 Subject: [PATCH 13/37] Fix linter complaints --- .../phase_estimators/hamiltonian_phase_estimation.py | 2 +- qiskit/algorithms/phase_estimators/phase_estimation.py | 1 - test/python/algorithms/test_phase_estimator.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 15962cc6ee7a..086f2728a982 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -133,7 +133,7 @@ def estimate(self, hamiltonian: OperatorBase, self._set_scale() unitary = self._get_unitary() return super().estimate(unitary=unitary, - state_preparation=state_preparation) + state_preparation=state_preparation) def _remove_identity(pauli_sum): diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 3a03d71da737..bf732144c4e5 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -75,7 +75,6 @@ def __init__(self, self._quantum_instance = quantum_instance - def _add_classical_register(self) -> None: """Add measurement instructions only if we are not using a state vector simulator.""" if not self._quantum_instance.is_statevector and not self._measurements_added: diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 5ac80c1290e6..16345de95626 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -86,7 +86,7 @@ def test_H2_hamiltonian(self): hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I ^ Z)) \ + (-0.3979374248431802 * (Z ^ I)) + (-0.011280104256235324 * (Z ^ Z)) \ + (0.18093119978423147 * (X ^ X)) - state_preparation = (I^H).to_circuit() + state_preparation = (I ^ H).to_circuit() evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) result = self.hamiltonian_pe(hamiltonian.to_pauli_op(), state_preparation, evolution=evo) with self.subTest('Most likely eigenvalues'): @@ -117,7 +117,7 @@ def _setup_from_bound(self, evolution): phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, quantum_instance=qi) result = phase_est.estimate(hamiltonian=hamiltonian, - bound=bound, + bound=bound, evolution=evolution, state_preparation=state_preparation) return result From c6f6dda61974334eb7ad02b74a92907017e7a9fa Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Sat, 27 Feb 2021 15:11:07 -0500 Subject: [PATCH 14/37] Allow PauliSumOp as input Hamiltonian --- .../phase_estimators/hamiltonian_phase_estimation.py | 7 +++++-- test/python/algorithms/test_phase_estimator.py | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 086f2728a982..780be2230d67 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -15,7 +15,7 @@ from typing import Optional, Union from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance -from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp +from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp, PauliSumOp from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from . import phase_estimation_scale @@ -114,7 +114,7 @@ def estimate(self, hamiltonian: OperatorBase, Raises: ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a - `SummedOp` whose terms are `PauliOp`s.) + `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) """ self._evolution = evolution @@ -127,6 +127,9 @@ def estimate(self, hamiltonian: OperatorBase, # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. # occupies more of the range of values representable by the qubit register. # The coefficient of this term will be added to the eigenvalues. + if isinstance(hamiltonian, PauliSumOp): + hamiltonian = hamiltonian.to_pauli_op() + id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) self._hamiltonian = hamiltonian_no_id self._id_coefficient = id_coefficient diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 16345de95626..8275aa9613dc 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -26,7 +26,7 @@ class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): def setUp(self): super().setUp() - self.hamiltonian_1 = ((0.5 * X) + Y + Z).to_pauli_op() + self.hamiltonian_1 = ((0.5 * X) + Y + Z) def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qubits=6, backend=qiskit.BasicAer.get_backend('statevector_simulator'), @@ -46,7 +46,7 @@ def test_pauli_sum_1(self): """Two eigenvalues from Pauli sum with X, Z""" a1 = 0.5 a2 = 1.0 - hamiltonian = ((a1 * X) + (a2 * Z)).to_pauli_op() + hamiltonian = ((a1 * X) + (a2 * Z)) state_preparation = H.to_circuit() result = self.hamiltonian_pe(hamiltonian, state_preparation) phase_dict = result.filter_phases(0.162, as_float=True) @@ -88,7 +88,7 @@ def test_H2_hamiltonian(self): + (0.18093119978423147 * (X ^ X)) state_preparation = (I ^ H).to_circuit() evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) - result = self.hamiltonian_pe(hamiltonian.to_pauli_op(), state_preparation, evolution=evo) + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) with self.subTest('Most likely eigenvalues'): self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=.001) with self.subTest('All eigenvalues'): @@ -100,7 +100,7 @@ def test_H2_hamiltonian(self): def test_matrix_evolution(self): """1Q Hamiltonian with MatrixEvolution""" - hamiltonian = ((0.5 * X) + (0.6 * Y) + (0.7 * I)).to_pauli_op() + hamiltonian = ((0.5 * X) + (0.6 * Y) + (0.7 * I)) state_preparation = None result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=MatrixEvolution()) phase_dict = result.filter_phases(0.2, as_float=True) @@ -111,7 +111,7 @@ def test_matrix_evolution(self): def _setup_from_bound(self, evolution): hamiltonian = self.hamiltonian_1 state_preparation = None - bound = 1.2 * sum([abs(hamiltonian.coeff * pauli.coeff) for pauli in hamiltonian]) + bound = 1.2 * sum([abs(hamiltonian.coeff * coeff) for coeff in hamiltonian.coeffs]) backend = qiskit.BasicAer.get_backend('statevector_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, From fd5fe0a8570a6509e7dc93d590b909232a117593 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Sat, 27 Feb 2021 19:49:32 -0500 Subject: [PATCH 15/37] Make suggested changes --- .../hamiltonian_phase_estimation_result.py | 2 +- .../algorithms/phase_estimators/phase_estimation.py | 12 ++++++------ .../phase_estimators/phase_estimation_result.py | 2 +- .../phase_estimators/phase_estimation_scale.py | 13 +++++++++---- .../algorithms/phase_estimators/phase_estimator.py | 3 ++- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 6064c2884df5..72c78bb0372a 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -50,7 +50,7 @@ def __init__(self, num_evaluation_qubits: int, circuit_result: Result, super().__init__(num_evaluation_qubits, circuit_result, phases) # pylint: disable=arguments-differ - def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, # type: ignore + def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, as_float: bool = True) -> Dict[Union[str, float], float]: """Filter phases as does `PhaseEstimatorResult.filter_phases`, with the addition that `phi` is shifted and translated to return eigenvalues diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index bf732144c4e5..eb261737f241 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -19,7 +19,7 @@ from qiskit.circuit import QuantumCircuit import qiskit from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import BaseBackend +from qiskit.providers import BaseBackend, Backend from qiskit.utils import QuantumInstance from qiskit.result import Result from .phase_estimation_result import PhaseEstimationResult, _sort_phases @@ -57,7 +57,8 @@ class PhaseEstimation(PhaseEstimator): def __init__(self, num_evaluation_qubits: int, - quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: + quantum_instance: Optional[Union[QuantumInstance, + BaseBackend, Backend]] = None) -> None: """Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will @@ -73,6 +74,8 @@ def __init__(self, if num_evaluation_qubits is not None: self._num_evaluation_qubits = num_evaluation_qubits + if isinstance(quantum_instance, (Backend, BaseBackend)): + quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance def _add_classical_register(self) -> None: @@ -170,10 +173,7 @@ def estimate(self, """ super().estimate(num_evaluation_qubits, unitary, state_preparation, pe_circuit, num_unitary_qubits) - if hasattr(self._quantum_instance, 'execute'): - circuit_result = self._quantum_instance.execute(self.construct_circuit()) - else: - circuit_result = self._quantum_instance.run(self.construct_circuit()) + circuit_result = self._quantum_instance.execute(self.construct_circuit()) phases = self._compute_phases(circuit_result) return self._result(circuit_result, phases) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py index 2a2f270c764d..6fc0b837081d 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_result.py @@ -94,7 +94,7 @@ def filter_phases(self, cutoff: float = 0.0, phases = {_bit_string_to_phase(k): counts[k] for k in counts.keys() if counts[k] > cutoff} else: - phases = {k: counts[k] for k in counts.keys() if counts[k] > cutoff} # type: ignore + phases = {k: counts[k] for k in counts.keys() if counts[k] > cutoff} else: phases = {} diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index fd3505838d50..71ab9a602b9e 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -50,11 +50,16 @@ def __init__(self, bound: float) -> None: @property def scale(self) -> float: - r"""Return the scale factor by which a Hermitian operator must be multiplied + r"""Return the Hamiltonian scaling factor. + + Return the scale factor by which a Hermitian operator must be multiplied so that the phase of the corresponding unitary is restricted to :math:`[-\pi, \pi]`. This factor is computed from the bound on the absolute values of the eigenvalues of the operator. The methods `scale_phase` and `scale_phases` are used recover the eigenvalues corresponding the original (unscaled) Hermitian operator. + + Returns: + The scale factor. """ return numpy.pi / self._bound @@ -77,7 +82,7 @@ def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: id_coefficient: All eigenvalues are shifted by this value. Returns: - An eigenvalue computed from the input phase. + An eigenvalue computed from the input phase. """ w = 2 * self._bound if phi <= 0.5: @@ -99,7 +104,7 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 id_coefficient: All eigenvalues are shifted by this value. Returns: - Eigenvalues computed from phases. + Eigenvalues computed from phases. """ w = 2 * self._bound if isinstance(phases, list): @@ -127,7 +132,7 @@ def from_pauli_sum(pauli_sum: SummedOp) -> PhaseEstimationScale: ValueError: if `pauli_sum` is not a sum of Pauli operators.' Returns: - A `PhaseEstimationScale` object + A `PhaseEstimationScale` object """ if pauli_sum.primitive_strings() != {'Pauli'}: raise ValueError( diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py index e7a661c5af7f..5aea58d8c33e 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -60,10 +60,11 @@ def estimate(self, self._num_unitary_qubits = num_unitary_qubits if state_preparation is not None: - self._pe_circuit = self._pe_circuit.compose( + self._pe_circuit.compose( state_preparation, qubits=range(self._num_evaluation_qubits, self._num_evaluation_qubits + self._num_unitary_qubits), + inplace=True, front=True) return PhaseEstimatorResult() From bc834b08258a76a23d9a9d48f06ebae47c6bfacc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 1 Mar 2021 08:54:54 -0500 Subject: [PATCH 16/37] Move module method to classmethod --- .../hamiltonian_phase_estimation.py | 3 +- .../phase_estimation_scale.py | 40 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 780be2230d67..0ace38ce4104 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -18,7 +18,6 @@ from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp, PauliSumOp from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation -from . import phase_estimation_scale from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult from .phase_estimation_scale import PhaseEstimationScale @@ -63,7 +62,7 @@ def __init__(self, def _set_scale(self) -> None: if self._bound is None: - pe_scale = phase_estimation_scale.from_pauli_sum(self._hamiltonian) + pe_scale = PhaseEstimationScale.from_pauli_sum(self._hamiltonian) self._pe_scale = pe_scale else: self._pe_scale = PhaseEstimationScale(self._bound) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 71ab9a602b9e..0f59186e2a15 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -116,28 +116,28 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 return phases + @classmethod + def from_pauli_sum(cls, pauli_sum: SummedOp): + """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. -def from_pauli_sum(pauli_sum: SummedOp) -> PhaseEstimationScale: - """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. + It is assumed that the `SummedOp` `pauli_sum` is the sum of `PauliOp`s. The bound on + the absolute value of the eigenvalues of the sum is obtained as the sum of the + absolute values of the coefficients of the terms. This is the best bound available in + the generic case. A `PhaseEstimationScale` object is instantiated using this bound. - It is assumed that the `SummedOp` `pauli_sum` is the sum of `PauliOp`s. The bound on - the absolute value of the eigenvalues of the sum is obtained as the sum of the - absolute values of the coefficients of the terms. This is the best bound available in - the generic case. A `PhaseEstimationScale` object is instantiated using this bound. - - Args: - pauli_sum: A `SummedOp` whose terms are `PauliOp`s. + Args: + pauli_sum: A `SummedOp` whose terms are `PauliOp`s. - Raises: - ValueError: if `pauli_sum` is not a sum of Pauli operators.' + Raises: + ValueError: if `pauli_sum` is not a sum of Pauli operators.' - Returns: - A `PhaseEstimationScale` object - """ - if pauli_sum.primitive_strings() != {'Pauli'}: - raise ValueError( - '`pauli_sum` must be a sum of Pauli operators. Got primitives {}.'.format( - pauli_sum.primitive_strings())) + Returns: + A `PhaseEstimationScale` object + """ + if pauli_sum.primitive_strings() != {'Pauli'}: + raise ValueError( + '`pauli_sum` must be a sum of Pauli operators. Got primitives {}.'.format( + pauli_sum.primitive_strings())) - bound = sum([abs(pauli_sum.coeff * pauli.coeff) for pauli in pauli_sum]) - return PhaseEstimationScale(bound) + bound = sum([abs(pauli_sum.coeff * pauli.coeff) for pauli in pauli_sum]) + return PhaseEstimationScale(bound) From 183f2813e7d1de647e7378b236d7ef15b991b778 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 1 Mar 2021 09:04:10 -0500 Subject: [PATCH 17/37] Fix indentation --- .../phase_estimators/phase_estimation.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index eb261737f241..0ce322f4a881 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -62,12 +62,12 @@ def __init__(self, """Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. + be estimated as a binary string with this many bits. quantum_instance: The quantum instance on which the circuit will be run. Raises: ValueError: unless only one of `unitary` and `pe_circuit` is `None`. - `num_unitary_qubits` disagrees with size of `unitary`. + `num_unitary_qubits` disagrees with size of `unitary`. """ self._measurements_added = False @@ -124,7 +124,7 @@ def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, circuit_result: the result object returned by the backend that ran the QPE circuit. Returns: - Either a dict or numpy.ndarray representing the frequencies of the phases. + Either a dict or numpy.ndarray representing the frequencies of the phases. """ if self._quantum_instance.is_statevector: @@ -156,17 +156,17 @@ def estimate(self, Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. + be estimated as a binary string with this many bits. unitary: The circuit representing the unitary operator whose eigenvalues (via phase) - will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. + will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. 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. + 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. pe_circuit: The phase estimation circuit. num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit` - if `pe_circuit` is passed. This parameter will be set from `unitary` - if `unitary` is passed. + if `pe_circuit` is passed. This parameter will be set from `unitary` + if `unitary` is passed. Returns: An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. From a22c4594755ab38653fe4e3eee17fc0bbab71b88 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 1 Mar 2021 09:29:29 -0500 Subject: [PATCH 18/37] Refactor scale_phase scale_phases --- .../algorithms/phase_estimators/phase_estimation_scale.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 0f59186e2a15..64381bcd2b4f 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -106,13 +106,10 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 Returns: Eigenvalues computed from phases. """ - w = 2 * self._bound if isinstance(phases, list): - phases = [(x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient) - for x in phases] + phases = [self.scale_phase(x, id_coefficient) for x in phases] else: - phases = {((x * w) + id_coefficient if x <= 0.5 else (((x - 1) * w) + id_coefficient)): - phases[x] for x in phases.keys()} + phases = {self.scale_phase(x, id_coefficient): phases[x] for x in phases.keys()} return phases From 893962790810e39310fd8677644a10be71d81d7d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 4 Mar 2021 10:00:20 -0500 Subject: [PATCH 19/37] Make HamiltonianPhaseEstimation have a hasa rather than isa use of PE --- .../hamiltonian_phase_estimation.py | 27 +++++++++++-------- .../hamiltonian_phase_estimation_result.py | 20 +++++++------- .../phase_estimators/phase_estimation.py | 5 +--- .../python/algorithms/test_phase_estimator.py | 3 --- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 0ace38ce4104..6035fd249822 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -22,7 +22,7 @@ from .phase_estimation_scale import PhaseEstimationScale -class HamiltonianPhaseEstimation(PhaseEstimation): +class HamiltonianPhaseEstimation: r"""Run the Quantum Phase Estimation algorithm to find the eigenvalues of a Hermitian operator. This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimator`, differing only @@ -49,16 +49,23 @@ class HamiltonianPhaseEstimation(PhaseEstimation): `arXiv:1809.09697 `_ """ + def __init__(self, num_evaluation_qubits: int, quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: + """Args: + num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will + be estimated as a binary string with this many bits. + quantum_instance: The quantum instance on which the circuit will be run. + """ self._pe_scale = None self._bound = None self._evolution = None self._hamiltonian = None self._id_coefficient = None - super().__init__(num_evaluation_qubits=num_evaluation_qubits, - quantum_instance=quantum_instance) + self._phase_estimation = PhaseEstimation( + num_evaluation_qubits=num_evaluation_qubits, + quantum_instance=quantum_instance) def _set_scale(self) -> None: if self._bound is None: @@ -86,11 +93,6 @@ def _get_unitary(self) -> QuantumCircuit: # It does not break any others that we tested. return unitary_circuit.decompose().decompose() - def _result(self, circuit_result, phases) -> HamiltonianPhaseEstimationResult: - return HamiltonianPhaseEstimationResult( - self._num_evaluation_qubits, phases=phases, id_coefficient=self._id_coefficient, - circuit_result=circuit_result, phase_estimation_scale=self._pe_scale) - # pylint: disable=arguments-differ def estimate(self, hamiltonian: OperatorBase, evolution: EvolutionBase, @@ -134,9 +136,12 @@ def estimate(self, hamiltonian: OperatorBase, self._id_coefficient = id_coefficient self._set_scale() unitary = self._get_unitary() - return super().estimate(unitary=unitary, - state_preparation=state_preparation) - + phase_estimation_result = self._phase_estimation.estimate( + unitary=unitary, state_preparation=state_preparation) + return HamiltonianPhaseEstimationResult( + phase_estimation_result=phase_estimation_result, + id_coefficient=self._id_coefficient, + phase_estimation_scale=self._pe_scale) def _remove_identity(pauli_sum): """Remove any identity operators from `pauli_sum`. Return diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 72c78bb0372a..2216b059f6f4 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -20,7 +20,7 @@ from .phase_estimation_scale import PhaseEstimationScale -class HamiltonianPhaseEstimationResult(PhaseEstimationResult): +class HamiltonianPhaseEstimationResult: """Store and manipulate results from running `HamiltonianPhaseEstimation`. This API of this class is nearly the same as `PhaseEstimatorResult`, differing only in @@ -28,26 +28,26 @@ class HamiltonianPhaseEstimationResult(PhaseEstimationResult): is `False`, then the phases are not translated and scaled to recover the eigenvalues of the Hamiltonian. Instead `phi` in :math:`[0, 1)` is returned, as is the case when then unitary is not derived from a Hamiltonian. + + This class is meant to be instantiated via `HamiltonianPhaseEstimation.estimate`. """ - def __init__(self, num_evaluation_qubits: int, circuit_result: Result, + def __init__(self, + phase_estimation_result: PhaseEstimationResult, phase_estimation_scale: PhaseEstimationScale, id_coefficient: float, - phases: Union[numpy.ndarray, Dict[str, float]]) -> None: + ) -> None: """ Args: - num_evaluation_qubits: number of qubits in phase-readout register. - circuit_result: result object returned by method running circuit. + phase_estimation_result: The result object returned by PhaseEstimation.estimate. phase_estimation_scale: object used to scale phases to obtain eigenvalues. id_coefficient: The coefficient of the identity term in the Hamiltonian. Eigenvalues are computed without this term so that the coefficient must added to give correct eigenvalues. This is done automatically when retrieving eigenvalues. - phases: ndarray or dict of phases and frequencies determined by QPE. """ self._phase_estimation_scale = phase_estimation_scale self._id_coefficient = id_coefficient - - super().__init__(num_evaluation_qubits, circuit_result, phases) + self._phase_estimation_result = phase_estimation_result # pylint: disable=arguments-differ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, @@ -73,7 +73,7 @@ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, if scaled and not as_float: raise ValueError('`as_float` must be `True` if `scaled` is `True`.') - phases = super().filter_phases(cutoff, as_float=as_float) + phases = self._phase_estimation_result.filter_phases(cutoff, as_float=as_float) if scaled: return cast(Dict, self._phase_estimation_scale.scale_phases(phases, self._id_coefficient)) @@ -90,5 +90,5 @@ def most_likely_eigenvalue(self) -> float: Returns: The most likely eigenvalue of the Hamiltonian. """ - phase = super().most_likely_phase + phase = self._phase_estimation_result.most_likely_phase return self._phase_estimation_scale.scale_phase(phase, self._id_coefficient) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 0ce322f4a881..655f6fe52910 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -65,7 +65,7 @@ def __init__(self, be estimated as a binary string with this many bits. quantum_instance: The quantum instance on which the circuit will be run. - Raises: + Raises: # TODO: fix this while refactoring ValueError: unless only one of `unitary` and `pe_circuit` is `None`. `num_unitary_qubits` disagrees with size of `unitary`. """ @@ -175,8 +175,5 @@ def estimate(self, pe_circuit, num_unitary_qubits) circuit_result = self._quantum_instance.execute(self.construct_circuit()) phases = self._compute_phases(circuit_result) - return self._result(circuit_result, phases) - - def _result(self, circuit_result, phases) -> PhaseEstimationResult: return PhaseEstimationResult(self._num_evaluation_qubits, circuit_result=circuit_result, phases=phases) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 8275aa9613dc..b94591e24735 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -134,9 +134,6 @@ def test_from_bound(self): phases = result.filter_phases(cutoff, scaled=False) with self.subTest('test unscaled phases are correct'): self.assertEqual(list(phases.keys()), [0.25, 0.75]) - with self.subTest('test most_likely_phase method'): - self.assertEqual(result.most_likely_eigenvalue, 1.5) - self.assertEqual(result.most_likely_phase, 0.25) def test_trotter_from_bound(self): """HamiltonianPhaseEstimation with bound and Trotterization""" From 244e0e8cff68e0590aa5f9a10f6186f667a2c2d2 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 4 Mar 2021 12:59:24 -0500 Subject: [PATCH 20/37] Remove all computation from PhaseEstimator * Remove ability to set number of computation qubits in PhaseEstimator.estimate() --- .../phase_estimators/phase_estimation.py | 31 ++++++++++++++++--- .../phase_estimators/phase_estimator.py | 27 ---------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 655f6fe52910..3a9869350650 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -18,6 +18,7 @@ import numpy from qiskit.circuit import QuantumCircuit import qiskit +import qiskit.circuit as circuit from qiskit.circuit.classicalregister import ClassicalRegister from qiskit.providers import BaseBackend, Backend from qiskit.utils import QuantumInstance @@ -147,7 +148,6 @@ def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, return phases def estimate(self, - num_evaluation_qubits: Optional[int] = None, unitary: Optional[QuantumCircuit] = None, state_preparation: Optional[QuantumCircuit] = None, pe_circuit: Optional[QuantumCircuit] = None, @@ -155,8 +155,6 @@ def estimate(self, """Run the circuit and return and return `PhaseEstimationResult`. Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. unitary: The circuit representing the unitary operator whose eigenvalues (via phase) will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. state_preparation: The circuit that prepares the state whose eigenphase will be @@ -171,8 +169,31 @@ def estimate(self, Returns: An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. """ - super().estimate(num_evaluation_qubits, unitary, state_preparation, - pe_circuit, num_unitary_qubits) + + if unitary is not None: + if pe_circuit is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._num_unitary_qubits = unitary.num_qubits + self._pe_circuit = circuit.library.PhaseEstimation(self._num_evaluation_qubits, unitary) + self._measurements_added = False + + if pe_circuit is not None: + if unitary is not None: + raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') + self._pe_circuit = pe_circuit + self._measurements_added = False + + if num_unitary_qubits is not None: + self._num_unitary_qubits = num_unitary_qubits + + if state_preparation is not None: + self._pe_circuit.compose( + state_preparation, + qubits=range(self._num_evaluation_qubits, + self._num_evaluation_qubits + self._num_unitary_qubits), + inplace=True, + front=True) + circuit_result = self._quantum_instance.execute(self.construct_circuit()) phases = self._compute_phases(circuit_result) return PhaseEstimationResult(self._num_evaluation_qubits, circuit_result=circuit_result, diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py index 5aea58d8c33e..2bc26e90483b 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -32,7 +32,6 @@ class PhaseEstimator(ABC): @abstractmethod def estimate(self, - num_evaluation_qubits: Optional[int], unitary: Optional[QuantumCircuit] = None, state_preparation: Optional[QuantumCircuit] = None, pe_circuit: Optional[QuantumCircuit] = None, @@ -40,32 +39,6 @@ def estimate(self, """ Estimate the phase. """ - if num_evaluation_qubits is not None: - self._num_evaluation_qubits = num_evaluation_qubits - - if unitary is not None: - if pe_circuit is not None: - raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._num_unitary_qubits = unitary.num_qubits - self._pe_circuit = circuit.library.PhaseEstimation(self._num_evaluation_qubits, unitary) - self._measurements_added = False - - if pe_circuit is not None: - if unitary is not None: - raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._pe_circuit = pe_circuit - self._measurements_added = False - - if num_unitary_qubits is not None: - self._num_unitary_qubits = num_unitary_qubits - - if state_preparation is not None: - self._pe_circuit.compose( - state_preparation, - qubits=range(self._num_evaluation_qubits, - self._num_evaluation_qubits + self._num_unitary_qubits), - inplace=True, - front=True) return PhaseEstimatorResult() From 5d426b5cbacc2707013507fb2a557dcc06ae7747 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 16 Mar 2021 14:38:43 +0100 Subject: [PATCH 21/37] update to current algorithm paradigm + small fixes * make algorithms stateless (store no problem information) * fix lint and docstrings --- .../hamiltonian_phase_estimation.py | 47 +++++----- .../hamiltonian_phase_estimation_result.py | 7 +- .../phase_estimators/phase_estimation.py | 94 ++++++++++--------- .../phase_estimation_result.py | 13 ++- .../phase_estimation_scale.py | 2 +- .../phase_estimators/phase_estimator.py | 28 ++---- 6 files changed, 96 insertions(+), 95 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 6035fd249822..adba64a982e7 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -49,7 +49,6 @@ class HamiltonianPhaseEstimation: `arXiv:1809.09697 `_ """ - def __init__(self, num_evaluation_qubits: int, quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: @@ -58,31 +57,25 @@ def __init__(self, be estimated as a binary string with this many bits. quantum_instance: The quantum instance on which the circuit will be run. """ - self._pe_scale = None - self._bound = None - self._evolution = None - self._hamiltonian = None - self._id_coefficient = None self._phase_estimation = PhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, quantum_instance=quantum_instance) - def _set_scale(self) -> None: - if self._bound is None: - pe_scale = PhaseEstimationScale.from_pauli_sum(self._hamiltonian) - self._pe_scale = pe_scale - else: - self._pe_scale = PhaseEstimationScale(self._bound) + def _get_scale(self, hamiltonian, bound=None) -> None: + if bound is None: + return PhaseEstimationScale.from_pauli_sum(hamiltonian) + + return PhaseEstimationScale(bound) - def _get_unitary(self) -> QuantumCircuit: + def _get_unitary(self, hamiltonian, pe_scale, evolution) -> QuantumCircuit: """Evolve the Hamiltonian to obtain a unitary. Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound and compute the unitary by applying the evolution object. """ # scale so that phase does not wrap. - scaled_hamiltonian = -self._pe_scale.scale * self._hamiltonian - unitary = self._evolution.convert(scaled_hamiltonian.exp_i()) + scaled_hamiltonian = -pe_scale.scale * hamiltonian + unitary = evolution.convert(scaled_hamiltonian.exp_i()) if not isinstance(unitary, QuantumCircuit): unitary_circuit = unitary.to_circuit() else: @@ -110,16 +103,13 @@ def estimate(self, hamiltonian: OperatorBase, then a bound will be computed. Returns: - HamiltonianPhaseEstimationResult instance containing the result of the estimation + HamiltonianPhaseEstimationResult instance containing the result of the estimation and diagnostic information. Raises: ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) """ - - self._evolution = evolution - self._bound = bound # The term propto the identity is removed from hamiltonian. # This is done for three reasons: # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some @@ -131,17 +121,24 @@ def estimate(self, hamiltonian: OperatorBase, if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() + # remove identitiy terms id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) - self._hamiltonian = hamiltonian_no_id - self._id_coefficient = id_coefficient - self._set_scale() - unitary = self._get_unitary() + + # get the rescaling object + pe_scale = self._get_scale(hamiltonian_no_id, bound) + + # get the unitary + unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) + + # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) + return HamiltonianPhaseEstimationResult( phase_estimation_result=phase_estimation_result, - id_coefficient=self._id_coefficient, - phase_estimation_scale=self._pe_scale) + id_coefficient=id_coefficient, + phase_estimation_scale=pe_scale) + def _remove_identity(pauli_sum): """Remove any identity operators from `pauli_sum`. Return diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 2216b059f6f4..994607c37c35 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -14,13 +14,12 @@ from typing import Dict, Union, cast -import numpy -from qiskit.result import Result +from qiskit.algorithms import AlgorithmResult from .phase_estimation_result import PhaseEstimationResult from .phase_estimation_scale import PhaseEstimationScale -class HamiltonianPhaseEstimationResult: +class HamiltonianPhaseEstimationResult(AlgorithmResult): """Store and manipulate results from running `HamiltonianPhaseEstimation`. This API of this class is nearly the same as `PhaseEstimatorResult`, differing only in @@ -31,6 +30,7 @@ class HamiltonianPhaseEstimationResult: This class is meant to be instantiated via `HamiltonianPhaseEstimation.estimate`. """ + def __init__(self, phase_estimation_result: PhaseEstimationResult, phase_estimation_scale: PhaseEstimationScale, @@ -45,6 +45,7 @@ def __init__(self, coefficient must added to give correct eigenvalues. This is done automatically when retrieving eigenvalues. """ + super().__init__() self._phase_estimation_scale = phase_estimation_scale self._id_coefficient = id_coefficient self._phase_estimation_result = phase_estimation_result diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index 3a9869350650..a7d6bfaec677 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -60,7 +60,6 @@ def __init__(self, num_evaluation_qubits: int, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None) -> None: - """Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. @@ -79,29 +78,46 @@ def __init__(self, quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance - def _add_classical_register(self) -> None: - """Add measurement instructions only if we are not using a state vector simulator.""" - if not self._quantum_instance.is_statevector and not self._measurements_added: - # Measure only the evaluation qubits. - regname = 'meas' - circ = self._pe_circuit - creg = ClassicalRegister(self._num_evaluation_qubits, regname) - circ.add_register(creg) - circ.barrier() - circ.measure(range(self._num_evaluation_qubits), range(self._num_evaluation_qubits)) - self._measurements_added = True - - def construct_circuit(self) -> QuantumCircuit: + def construct_circuit(self, + unitary: QuantumCircuit, + state_preparation: Optional[QuantumCircuit] = None) -> QuantumCircuit: """Return the circuit to be executed to estimate phases. This circuit includes as sub-circuits the core phase estimation circuit, with the addition of the state-preparation circuit and possibly measurement instructions. """ - self._add_classical_register() - return self._pe_circuit + num_evaluation_qubits = self._num_evaluation_qubits + num_unitary_qubits = unitary.num_qubits + + pe_circuit = circuit.library.PhaseEstimation(num_evaluation_qubits, unitary) + + if state_preparation is not None: + pe_circuit.compose( + state_preparation, + qubits=range(num_evaluation_qubits, + num_evaluation_qubits + num_unitary_qubits), + inplace=True, + front=True) - def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, - qiskit.result.Counts]: + self._add_measurement_if_required(pe_circuit) + + return pe_circuit + + def _add_measurement_if_required(self, pe_circuit): + if not self._quantum_instance.is_statevector: + # Measure only the evaluation qubits. + regname = 'meas' + creg = ClassicalRegister(self._num_evaluation_qubits, regname) + pe_circuit.add_register(creg) + pe_circuit.barrier() + pe_circuit.measure(range(self._num_evaluation_qubits), + range(self._num_evaluation_qubits)) + + return circuit + + def _compute_phases(self, + num_unitary_qubits: int, + circuit_result: Result) -> Union[numpy.ndarray, qiskit.result.Counts]: """Compute frequencies/counts of phases from the result of running the QPE circuit. How the frequencies are computed depends on whether the backend computes amplitude or @@ -122,6 +138,7 @@ def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, they can be easily understood when displaying or plotting a histogram. Args: + num_unitary_qubits: The number of qubits in the unitary. circuit_result: the result object returned by the backend that ran the QPE circuit. Returns: @@ -133,7 +150,7 @@ def _compute_phases(self, circuit_result: Result) -> Union[numpy.ndarray, evaluation_density_matrix = qiskit.quantum_info.partial_trace( state_vec, range(self._num_evaluation_qubits, - self._num_evaluation_qubits + self._num_unitary_qubits) + self._num_evaluation_qubits + num_unitary_qubits) ) phases = evaluation_density_matrix.probabilities() else: @@ -152,9 +169,9 @@ def estimate(self, state_preparation: Optional[QuantumCircuit] = None, pe_circuit: Optional[QuantumCircuit] = None, num_unitary_qubits: Optional[int] = None) -> PhaseEstimationResult: - """Run the circuit and return and return `PhaseEstimationResult`. + """Run the the phase estimation algorithm. - Args: + Args: unitary: The circuit representing the unitary operator whose eigenvalues (via phase) will be measured. Exactly one of `pe_circuit` and `unitary` must be passed. state_preparation: The circuit that prepares the state whose eigenphase will be @@ -166,35 +183,28 @@ def estimate(self, if `pe_circuit` is passed. This parameter will be set from `unitary` if `unitary` is passed. + Raises: + ValueError: If both `pe_circuit` and `unitary` are passed. + ValueError: If neither `pe_circuit` nor `unitary` are passed. + Returns: An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. """ + num_evaluation_qubits = self._num_evaluation_qubits if unitary is not None: if pe_circuit is not None: raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._num_unitary_qubits = unitary.num_qubits - self._pe_circuit = circuit.library.PhaseEstimation(self._num_evaluation_qubits, unitary) - self._measurements_added = False + pe_circuit = self.construct_circuit(unitary, state_preparation) + num_unitary_qubits = unitary.num_qubits - if pe_circuit is not None: - if unitary is not None: - raise ValueError('Only one of `pe_circuit` and `unitary` may be passed.') - self._pe_circuit = pe_circuit - self._measurements_added = False + elif pe_circuit is not None: + self._add_measurement_if_required(pe_circuit) - if num_unitary_qubits is not None: - self._num_unitary_qubits = num_unitary_qubits - - if state_preparation is not None: - self._pe_circuit.compose( - state_preparation, - qubits=range(self._num_evaluation_qubits, - self._num_evaluation_qubits + self._num_unitary_qubits), - inplace=True, - front=True) + else: + raise ValueError('One of `pe_circuit` and `unitary` must be passed.') - circuit_result = self._quantum_instance.execute(self.construct_circuit()) - phases = self._compute_phases(circuit_result) - return PhaseEstimationResult(self._num_evaluation_qubits, circuit_result=circuit_result, + circuit_result = self._quantum_instance.execute(pe_circuit) + phases = self._compute_phases(num_unitary_qubits, circuit_result) + return PhaseEstimationResult(num_evaluation_qubits, circuit_result=circuit_result, phases=phases) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py index 6fc0b837081d..8416c6353fae 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_result.py @@ -36,12 +36,21 @@ def __init__(self, num_evaluation_qubits: int, circuit_result: result object returned by method running circuit. phases: ndarray or dict of phases and frequencies determined by QPE. """ - # int: number of qubits in phase-readout register - self._num_evaluation_qubits = num_evaluation_qubits + super().__init__() self._phases = phases + # int: number of qubits in phase-readout register + self._num_evaluation_qubits = num_evaluation_qubits self._circuit_result = circuit_result + @property + def phases(self) -> Union[numpy.ndarray, dict]: + """Return all phases and their frequencies computed by QPE. + + This is an array or dict whose values correspond to weights on bit strings. + """ + return self._phases + @property def circuit_result(self) -> Result: """Return the result object returned by running the QPE circuit (on hardware or simulator). diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 64381bcd2b4f..7c9dc98a80f0 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -114,7 +114,7 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 return phases @classmethod - def from_pauli_sum(cls, pauli_sum: SummedOp): + def from_pauli_sum(cls, pauli_sum: SummedOp) -> 'PhaseEstimationScale': """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. It is assumed that the `SummedOp` `pauli_sum` is the sum of `PauliOp`s. The bound on diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py index 2bc26e90483b..58d8a6f69160 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -12,15 +12,11 @@ """The Phase Estimator interface""" -from typing import Union, Optional -from abc import ABC, abstractmethod -import numpy -import qiskit.circuit as circuit +from typing import Optional +from abc import ABC, abstractmethod, abstractproperty from qiskit.circuit import QuantumCircuit from qiskit.algorithms import AlgorithmResult -# pylint: disable=attribute-defined-outside-init - class PhaseEstimator(ABC): """The Phase Estimator interface @@ -36,26 +32,14 @@ def estimate(self, state_preparation: Optional[QuantumCircuit] = None, pe_circuit: Optional[QuantumCircuit] = None, num_unitary_qubits: Optional[int] = None) -> 'PhaseEstimatorResult': - """ - Estimate the phase. - """ - - return PhaseEstimatorResult() + """Estimate the phase.""" + raise NotImplementedError class PhaseEstimatorResult(AlgorithmResult): """Phase Estimator Result.""" - @property - def phases(self) -> Union[numpy.ndarray, dict]: - """Return all phases and their frequencies computed by QPE. - - This is an array or dict whose values correspond to weights on bit strings. - """ - # pylint: disable=no-member - return self._phases - - @property + @abstractproperty def most_likely_phase(self) -> float: r"""Return the estimated phase as a number in :math:`[0.0, 1.0)`. @@ -63,4 +47,4 @@ def most_likely_phase(self) -> float: eigenvector of the unitary so that the peak of the probability density occurs at the bit string that most closely approximates the true phase. """ - raise NotImplementedError() + raise NotImplementedError From 246209b01ede511ad50b7647ec5b456c1ab942a4 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 16 Mar 2021 20:19:09 +0100 Subject: [PATCH 22/37] fix cyclic imports --- .../phase_estimators/hamiltonian_phase_estimation_result.py | 2 +- qiskit/algorithms/phase_estimators/phase_estimator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 994607c37c35..d9003592ad96 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -14,7 +14,7 @@ from typing import Dict, Union, cast -from qiskit.algorithms import AlgorithmResult +from qiskit.algorithms.algorithm_result import AlgorithmResult from .phase_estimation_result import PhaseEstimationResult from .phase_estimation_scale import PhaseEstimationScale diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py index 58d8a6f69160..bd492316e971 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -15,7 +15,7 @@ from typing import Optional from abc import ABC, abstractmethod, abstractproperty from qiskit.circuit import QuantumCircuit -from qiskit.algorithms import AlgorithmResult +from qiskit.algorithms.algorithm_result import AlgorithmResult class PhaseEstimator(ABC): From bf4184fa999bb825c2869c4a99423ab125327594 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 16 Mar 2021 19:37:09 -0400 Subject: [PATCH 23/37] Use PauliTrotterEvolution by default in HamiltonianPhaseEstimation --- .../hamiltonian_phase_estimation.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index adba64a982e7..7576985e4aa6 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -15,7 +15,8 @@ from typing import Optional, Union from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance -from qiskit.opflow import EvolutionBase, OperatorBase, SummedOp, PauliSumOp +from qiskit.opflow import (EvolutionBase, PauliTrotterEvolution, OperatorBase, + SummedOp, PauliSumOp) from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult @@ -88,13 +89,14 @@ def _get_unitary(self, hamiltonian, pe_scale, evolution) -> QuantumCircuit: # pylint: disable=arguments-differ def estimate(self, hamiltonian: OperatorBase, - evolution: EvolutionBase, + evolution: Optional[EvolutionBase] = None, state_preparation: Optional[QuantumCircuit] = None, bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: """ Args: hamiltonian: a Hermitian operator. - evolution: An evolution object that generates a unitary from `hamiltonian`. + evolution: An evolution object that generates a unitary from `hamiltonian`. If + `None`, then the default `PauliTrotterEvolution` is used. 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. @@ -110,6 +112,13 @@ def estimate(self, hamiltonian: OperatorBase, ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) """ + if isinstance(hamiltonian, PauliSumOp): + hamiltonian = hamiltonian.to_pauli_op() + + if evolution is None: + evolution = PauliTrotterEvolution() + + # remove identitiy terms # The term propto the identity is removed from hamiltonian. # This is done for three reasons: # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some @@ -118,10 +127,6 @@ def estimate(self, hamiltonian: OperatorBase, # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. # occupies more of the range of values representable by the qubit register. # The coefficient of this term will be added to the eigenvalues. - if isinstance(hamiltonian, PauliSumOp): - hamiltonian = hamiltonian.to_pauli_op() - - # remove identitiy terms id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) # get the rescaling object From a5216f2124acb147b2da10c83a223ba9e717746a Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 17 Mar 2021 18:04:41 -0400 Subject: [PATCH 24/37] Make HamiltonianPhaseEstmation take a StateFn for state preparation --- .../phase_estimators/hamiltonian_phase_estimation.py | 12 +++++++----- test/python/algorithms/test_phase_estimator.py | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 7576985e4aa6..0c0c77a9e3d1 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -16,7 +16,7 @@ from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance from qiskit.opflow import (EvolutionBase, PauliTrotterEvolution, OperatorBase, - SummedOp, PauliSumOp) + SummedOp, PauliSumOp, StateFn) from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult @@ -89,17 +89,17 @@ def _get_unitary(self, hamiltonian, pe_scale, evolution) -> QuantumCircuit: # pylint: disable=arguments-differ def estimate(self, hamiltonian: OperatorBase, + state_preparation: Optional[StateFn] = None, evolution: Optional[EvolutionBase] = None, - state_preparation: Optional[QuantumCircuit] = None, bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: """ Args: hamiltonian: a Hermitian operator. - evolution: An evolution object that generates a unitary from `hamiltonian`. If - `None`, then the default `PauliTrotterEvolution` is used. - state_preparation: The circuit that prepares the state whose eigenphase will be + state_preparation: The `StateFn` to be prepared, 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. + evolution: An evolution object that generates a unitary from `hamiltonian`. If + `None`, then the default `PauliTrotterEvolution` is used. bound: An upper bound on the absolute value of the eigenvalues of `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case then a bound will be computed. @@ -135,6 +135,8 @@ def estimate(self, hamiltonian: OperatorBase, # get the unitary unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) + if state_preparation is not None: + state_preparation=state_preparation.to_circuit() # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index b94591e24735..f08a2e5797f9 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -18,7 +18,7 @@ from qiskit.algorithms.phase_estimators import PhaseEstimation, HamiltonianPhaseEstimation from qiskit.opflow.evolutions import PauliTrotterEvolution, MatrixEvolution import qiskit -from qiskit.opflow import (H, X, Y, Z, I) +from qiskit.opflow import (H, X, Y, Z, I, StateFn) class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): @@ -47,7 +47,7 @@ def test_pauli_sum_1(self): a1 = 0.5 a2 = 1.0 hamiltonian = ((a1 * X) + (a2 * Z)) - state_preparation = H.to_circuit() + state_preparation = StateFn(H.to_circuit()) result = self.hamiltonian_pe(hamiltonian, state_preparation) phase_dict = result.filter_phases(0.162, as_float=True) phases = list(phase_dict.keys()) @@ -86,7 +86,7 @@ def test_H2_hamiltonian(self): hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I ^ Z)) \ + (-0.3979374248431802 * (Z ^ I)) + (-0.011280104256235324 * (Z ^ Z)) \ + (0.18093119978423147 * (X ^ X)) - state_preparation = (I ^ H).to_circuit() + state_preparation = StateFn((I ^ H).to_circuit()) evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) with self.subTest('Most likely eigenvalues'): From afbfc4b51a23a9e46e0214aa9547ec8d2eb06abd Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 18 Mar 2021 11:03:57 -0400 Subject: [PATCH 25/37] Add method most_likely_phase to HamiltonianPhaseEstimation --- .../hamiltonian_phase_estimation_result.py | 9 +++++++++ test/python/algorithms/test_phase_estimator.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index d9003592ad96..36a32bb34196 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -81,6 +81,15 @@ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, else: return cast(Dict, phases) + @property + def most_likely_phase(self) -> float: + """The most likely phase of the unitary corresponding to the Hamiltonian. + + Returns: + The most likely phase. + """ + return self._phase_estimation_result.most_likely_phase + @property def most_likely_eigenvalue(self) -> float: """The most likely eigenvalue of the Hamiltonian. diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index f08a2e5797f9..5ce4c867cac2 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -91,6 +91,8 @@ def test_H2_hamiltonian(self): result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) with self.subTest('Most likely eigenvalues'): self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=.001) + with self.subTest('Most likely phase'): + self.assertAlmostEqual(result.most_likely_phase, 0.5937, delta=.001) with self.subTest('All eigenvalues'): phase_dict = result.filter_phases(0.1) phases = list(phase_dict.keys()) From 0bbd79f07132ab94ffe1dbedec180efb4c155507 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 18 Mar 2021 15:42:22 -0400 Subject: [PATCH 26/37] Allow PauliOp as input unitary to HPE --- .../hamiltonian_phase_estimation.py | 7 ++++++- .../python/algorithms/test_phase_estimator.py | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 0c0c77a9e3d1..a843cdf6985d 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -16,7 +16,7 @@ from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance from qiskit.opflow import (EvolutionBase, PauliTrotterEvolution, OperatorBase, - SummedOp, PauliSumOp, StateFn) + SummedOp, PauliOp, PauliSumOp, StateFn) from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult @@ -112,8 +112,13 @@ def estimate(self, hamiltonian: OperatorBase, ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) """ + if not isinstance(evolution, EvolutionBase): + raise TypeError(f'Expecting type EvolutionBase, got {type(evolution)}') + if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() + elif isinstance(hamiltonian, PauliOp): + hamiltonian = SummedOp([hamiltonian]) if evolution is None: evolution = PauliTrotterEvolution() diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 5ce4c867cac2..98c126981b98 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -30,7 +30,8 @@ def setUp(self): def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qubits=6, backend=qiskit.BasicAer.get_backend('statevector_simulator'), - evolution=MatrixEvolution()): + evolution=MatrixEvolution(), + bound=None): """Run HamiltonianPhaseEstimation and return result with all phases.""" quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation( @@ -38,7 +39,8 @@ def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qub quantum_instance=quantum_instance) result = phase_est.estimate( hamiltonian=hamiltonian, - state_preparation=state_preparation, evolution=evolution) + state_preparation=state_preparation, evolution=evolution, + bound=bound) return result # pylint: disable=invalid-name @@ -81,6 +83,20 @@ def test_pauli_sum_2(self): with self.subTest('Use PauliTrotterEvolution, second phase'): self.assertAlmostEqual(phases[1], -1.484, delta=0.001) + def test_single_pauli_op(self): + """Two eigenvalues from Pauli sum with X, Y, Z""" + hamiltonian = Z + state_preparation = None + result = self.hamiltonian_pe(hamiltonian, state_preparation) + eigv = result.most_likely_eigenvalue + with self.subTest('First eigenvalue'): + self.assertAlmostEqual(eigv, 1.0, delta=0.001) + state_preparation = StateFn(X.to_circuit()) + result = self.hamiltonian_pe(hamiltonian, state_preparation, bound=1.05) + eigv = result.most_likely_eigenvalue + with self.subTest('Second eigenvalue'): + self.assertAlmostEqual(eigv, -0.98, delta=0.01) + def test_H2_hamiltonian(self): """Test H2 hamiltonian""" hamiltonian = (-1.0523732457728587 * (I ^ I)) + (0.3979374248431802 * (I ^ Z)) \ From 50912bd53cbe3d48d94a9bb023861edbd69827c0 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 18 Mar 2021 19:19:32 -0400 Subject: [PATCH 27/37] Allow MatrixOp as input Hermitian op to HPE --- .../hamiltonian_phase_estimation.py | 58 ++++++++++++------- .../python/algorithms/test_phase_estimator.py | 28 +++++---- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index a843cdf6985d..f5aebb89cfa7 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -16,7 +16,7 @@ from qiskit import QuantumCircuit from qiskit.utils import QuantumInstance from qiskit.opflow import (EvolutionBase, PauliTrotterEvolution, OperatorBase, - SummedOp, PauliOp, PauliSumOp, StateFn) + SummedOp, PauliOp, MatrixOp, PauliSumOp, StateFn) from qiskit.providers import BaseBackend from .phase_estimation import PhaseEstimation from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult @@ -101,8 +101,10 @@ def estimate(self, hamiltonian: OperatorBase, evolution: An evolution object that generates a unitary from `hamiltonian`. If `None`, then the default `PauliTrotterEvolution` is used. bound: An upper bound on the absolute value of the eigenvalues of - `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, in which case - then a bound will be computed. + `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, or a + PauliOp, in which case a bound will be computed. If `hamiltonian` + is a `MatrixOp`, then `bound` may not be `None`. The tighter the bound, + the higher the resolution of computed phases. Returns: HamiltonianPhaseEstimationResult instance containing the result of the estimation @@ -115,30 +117,42 @@ def estimate(self, hamiltonian: OperatorBase, if not isinstance(evolution, EvolutionBase): raise TypeError(f'Expecting type EvolutionBase, got {type(evolution)}') + if evolution is None: + evolution = PauliTrotterEvolution() + if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() elif isinstance(hamiltonian, PauliOp): hamiltonian = SummedOp([hamiltonian]) - if evolution is None: - evolution = PauliTrotterEvolution() - - # remove identitiy terms - # The term propto the identity is removed from hamiltonian. - # This is done for three reasons: - # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some - # cases. - # 2. Allow working with a simpler Hamiltonian, one with fewer terms. - # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. - # occupies more of the range of values representable by the qubit register. - # The coefficient of this term will be added to the eigenvalues. - id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) - - # get the rescaling object - pe_scale = self._get_scale(hamiltonian_no_id, bound) - - # get the unitary - unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) + if isinstance(hamiltonian, SummedOp): + # remove identitiy terms + # The term propto the identity is removed from hamiltonian. + # This is done for three reasons: + # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some + # cases. + # 2. Allow working with a simpler Hamiltonian, one with fewer terms. + # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. + # occupies more of the range of values representable by the qubit register. + # The coefficient of this term will be added to the eigenvalues. + id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) + + # get the rescaling object + pe_scale = self._get_scale(hamiltonian_no_id, bound) + + # get the unitary + unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) + + elif isinstance(hamiltonian, MatrixOp): + if bound is None: + raise ValueError('bound must be specified if Hermitian operator is MatrixOp') + + # Do not subtract an identity term from the matrix, so do not compensate. + id_coefficient = 0.0 + pe_scale = self._get_scale(hamiltonian, bound) + unitary = self._get_unitary(hamiltonian, pe_scale, evolution) + else: + raise TypeError(f'Hermitian operator of type {type(hamiltonian)} not supported.') if state_preparation is not None: state_preparation=state_preparation.to_circuit() diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 98c126981b98..76b38a2047ea 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -126,10 +126,12 @@ def test_matrix_evolution(self): self.assertAlmostEqual(phases[0], 1.490, delta=0.001) self.assertAlmostEqual(phases[1], -0.090, delta=0.001) - def _setup_from_bound(self, evolution): + def _setup_from_bound(self, evolution, op_class): hamiltonian = self.hamiltonian_1 state_preparation = None bound = 1.2 * sum([abs(hamiltonian.coeff * coeff) for coeff in hamiltonian.coeffs]) + if op_class == 'MatrixOp': + hamiltonian = hamiltonian.to_matrix_op() backend = qiskit.BasicAer.get_backend('statevector_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, @@ -142,20 +144,22 @@ def _setup_from_bound(self, evolution): def test_from_bound(self): """HamiltonianPhaseEstimation with bound""" - result = self._setup_from_bound(MatrixEvolution()) - cutoff = 0.01 - phases = result.filter_phases(cutoff) - with self.subTest('test phases has the correct length'): - self.assertEqual(len(phases), 2) - with self.subTest('test scaled phases are correct'): - self.assertEqual(list(phases.keys()), [1.5, -1.5]) - phases = result.filter_phases(cutoff, scaled=False) - with self.subTest('test unscaled phases are correct'): - self.assertEqual(list(phases.keys()), [0.25, 0.75]) + for op_class in ('SummedOp', 'MatrixOp'): + result = self._setup_from_bound(MatrixEvolution(), op_class) + cutoff = 0.01 + phases = result.filter_phases(cutoff) + with self.subTest(f'test phases has the correct length: {op_class}'): + self.assertEqual(len(phases), 2) + with self.subTest(f'test scaled phases are correct: {op_class}'): + self.assertEqual(list(phases.keys()), [1.5, -1.5]) + phases = result.filter_phases(cutoff, scaled=False) + with self.subTest(f'test unscaled phases are correct: {op_class}'): + self.assertEqual(list(phases.keys()), [0.25, 0.75]) def test_trotter_from_bound(self): """HamiltonianPhaseEstimation with bound and Trotterization""" - result = self._setup_from_bound(PauliTrotterEvolution(trotter_mode='suzuki', reps=3)) + result = self._setup_from_bound(PauliTrotterEvolution(trotter_mode='suzuki', reps=3), + op_class='SummedOp') phase_dict = result.filter_phases(0.1) phases = list(phase_dict.keys()) with self.subTest('test phases has the correct length'): From 70fd2acd16c39fced0fd3a5f2f9596eea591401d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Fri, 19 Mar 2021 09:52:42 -0400 Subject: [PATCH 28/37] Fix linter complaints --- .../phase_estimators/hamiltonian_phase_estimation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index f5aebb89cfa7..4a397c9fbe69 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -112,7 +112,8 @@ def estimate(self, hamiltonian: OperatorBase, Raises: ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a - `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) + `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) + TypeError: if `evolution` is not of type `EvolutionBase`. """ if not isinstance(evolution, EvolutionBase): raise TypeError(f'Expecting type EvolutionBase, got {type(evolution)}') @@ -155,7 +156,7 @@ def estimate(self, hamiltonian: OperatorBase, raise TypeError(f'Hermitian operator of type {type(hamiltonian)} not supported.') if state_preparation is not None: - state_preparation=state_preparation.to_circuit() + state_preparation = state_preparation.to_circuit() # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) From 6b4bbac9a36c545b4642f60b2fbe14d66d9ac208 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 23 Mar 2021 17:10:06 +0100 Subject: [PATCH 29/37] fix evolution = None case --- .../phase_estimators/hamiltonian_phase_estimation.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 4a397c9fbe69..4c36914a1413 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -115,11 +115,10 @@ def estimate(self, hamiltonian: OperatorBase, `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) TypeError: if `evolution` is not of type `EvolutionBase`. """ - if not isinstance(evolution, EvolutionBase): - raise TypeError(f'Expecting type EvolutionBase, got {type(evolution)}') - if evolution is None: evolution = PauliTrotterEvolution() + elif not isinstance(evolution, EvolutionBase): + raise TypeError(f'Expecting type EvolutionBase, got {type(evolution)}') if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() @@ -156,7 +155,7 @@ def estimate(self, hamiltonian: OperatorBase, raise TypeError(f'Hermitian operator of type {type(hamiltonian)} not supported.') if state_preparation is not None: - state_preparation = state_preparation.to_circuit() + state_preparation = state_preparation.to_circuit_op().to_circuit() # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) From ac9d497ef8c3eb0526a4c01bda0a65e3f8435347 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 23 Mar 2021 17:16:15 +0100 Subject: [PATCH 30/37] refactor tests slightly * be explicit about which evolution is used * combine tests using ddt * add some linebreaks --- .../python/algorithms/test_phase_estimator.py | 129 +++++++----------- 1 file changed, 50 insertions(+), 79 deletions(-) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 76b38a2047ea..6f29b85a7742 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -13,6 +13,7 @@ """Test phase estimation""" import unittest +from ddt import ddt, data from test.python.algorithms import QiskitAlgorithmsTestCase import numpy as np from qiskit.algorithms.phase_estimators import PhaseEstimation, HamiltonianPhaseEstimation @@ -21,16 +22,13 @@ from qiskit.opflow import (H, X, Y, Z, I, StateFn) +@ddt class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): """Tests for obtaining eigenvalues from phase estimation""" - def setUp(self): - super().setUp() - self.hamiltonian_1 = ((0.5 * X) + Y + Z) - def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qubits=6, backend=qiskit.BasicAer.get_backend('statevector_simulator'), - evolution=MatrixEvolution(), + evolution=None, bound=None): """Run HamiltonianPhaseEstimation and return result with all phases.""" quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) @@ -43,55 +41,46 @@ def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qub bound=bound) return result - # pylint: disable=invalid-name - def test_pauli_sum_1(self): + @data(MatrixEvolution(), PauliTrotterEvolution('suzuki', 4)) + def test_pauli_sum_1(self, evolution): """Two eigenvalues from Pauli sum with X, Z""" - a1 = 0.5 - a2 = 1.0 - hamiltonian = ((a1 * X) + (a2 * Z)) + hamiltonian = 0.5 * X + Z state_preparation = StateFn(H.to_circuit()) - result = self.hamiltonian_pe(hamiltonian, state_preparation) - phase_dict = result.filter_phases(0.162, as_float=True) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], 1.125, delta=0.001) - self.assertAlmostEqual(phases[1], -1.125, delta=0.001) - evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=4) - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) + + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) phase_dict = result.filter_phases(0.162, as_float=True) phases = list(phase_dict.keys()) phases.sort() - with self.subTest('Use PauliTrotterEvolution, first phase'): - self.assertAlmostEqual(phases[0], -1.125, delta=0.001) - with self.subTest('Use PauliTrotterEvolution, second phase'): - self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - def test_pauli_sum_2(self): + self.assertAlmostEqual(phases[0], -1.125, delta=0.001) + self.assertAlmostEqual(phases[1], 1.125, delta=0.001) + + @data(MatrixEvolution(), PauliTrotterEvolution('suzuki', 3)) + def test_pauli_sum_2(self, evolution): """Two eigenvalues from Pauli sum with X, Y, Z""" - hamiltonian = self.hamiltonian_1 + hamiltonian = 0.5 * X + Y + Z state_preparation = None - result = self.hamiltonian_pe(hamiltonian, state_preparation) - phase_dict = result.filter_phases(0.1, as_float=True) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], 1.484, delta=0.001) - self.assertAlmostEqual(phases[1], -1.484, delta=0.001) - evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=3) - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) + + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) phase_dict = result.filter_phases(0.1, as_float=True) phases = list(phase_dict.keys()) - with self.subTest('Use PauliTrotterEvolution, first phase'): - self.assertAlmostEqual(phases[0], 1.484, delta=0.001) - with self.subTest('Use PauliTrotterEvolution, second phase'): - self.assertAlmostEqual(phases[1], -1.484, delta=0.001) + phases.sort() + + self.assertAlmostEqual(phases[0], -1.484, delta=0.001) + self.assertAlmostEqual(phases[1], 1.484, delta=0.001) def test_single_pauli_op(self): """Two eigenvalues from Pauli sum with X, Y, Z""" hamiltonian = Z state_preparation = None - result = self.hamiltonian_pe(hamiltonian, state_preparation) + + result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=None) eigv = result.most_likely_eigenvalue with self.subTest('First eigenvalue'): self.assertAlmostEqual(eigv, 1.0, delta=0.001) + state_preparation = StateFn(X.to_circuit()) + result = self.hamiltonian_pe(hamiltonian, state_preparation, bound=1.05) eigv = result.most_likely_eigenvalue with self.subTest('Second eigenvalue'): @@ -127,7 +116,7 @@ def test_matrix_evolution(self): self.assertAlmostEqual(phases[1], -0.090, delta=0.001) def _setup_from_bound(self, evolution, op_class): - hamiltonian = self.hamiltonian_1 + hamiltonian = 0.5 * X + Y + Z state_preparation = None bound = 1.2 * sum([abs(hamiltonian.coeff * coeff) for coeff in hamiltonian.coeffs]) if op_class == 'MatrixOp': @@ -169,6 +158,7 @@ def test_trotter_from_bound(self): self.assertAlmostEqual(phases[1], -1.5, delta=0.001) +@ddt class TestPhaseEstimation(QiskitAlgorithmsTestCase): """Evolution tests.""" @@ -188,56 +178,41 @@ def one_phase(self, unitary_circuit, state_preparation=None, n_eval_qubits=6, phase = result.most_likely_phase return phase - def test_qpe_Z0(self): + @data('qasm_simulator', 'statevector_simulator') + def test_qpe_Z0(self, backend_type): """eigenproblem Z, |0>""" + backend = qiskit.BasicAer.get_backend(backend_type) unitary_circuit = Z.to_circuit() state_preparation = None # prepare |0> - phase = self.one_phase(unitary_circuit, state_preparation) + phase = self.one_phase(unitary_circuit, state_preparation, backend=backend) self.assertEqual(phase, 0.0) - def test_qpe_Z0_statevector(self): - """eigenproblem Z, |0>, statevector simulator""" - - unitary_circuit = Z.to_circuit() - state_preparation = None # prepare |0> - phase = self.one_phase(unitary_circuit, state_preparation, - backend=qiskit.BasicAer.get_backend('statevector_simulator')) - self.assertEqual(phase, 0.0) - - def test_qpe_Z1(self): + @data('qasm_simulator', 'statevector_simulator') + def test_qpe_Z1(self, backend_type): """eigenproblem Z, |1>""" - unitary_circuit = Z.to_circuit() - state_preparation = X.to_circuit() # prepare |1> - phase = self.one_phase(unitary_circuit, state_preparation) - self.assertEqual(phase, 0.5) + backend = qiskit.BasicAer.get_backend(backend_type) - def test_qpe_Z1_estimate(self): - """eigenproblem Z, |1>, estimate interface""" unitary_circuit = Z.to_circuit() state_preparation = X.to_circuit() # prepare |1> - backend = qiskit.BasicAer.get_backend('statevector_simulator') - qi = qiskit.utils.QuantumInstance(backend=backend) - num_evaluation_qubits = 6 - pe = PhaseEstimation(num_evaluation_qubits, quantum_instance=qi) - result = pe.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - phase = result.most_likely_phase + phase = self.one_phase(unitary_circuit, state_preparation, backend=backend) self.assertEqual(phase, 0.5) - def test_qpe_Xplus(self): + @data('plus', 'minus') + def test_qpe_Xplus(self, state): """eigenproblem X, |+>""" unitary_circuit = X.to_circuit() - state_preparation = H.to_circuit() # prepare |+> - phase = self.one_phase(unitary_circuit, state_preparation) - self.assertEqual(phase, 0.0) + if state == 'minus': # prepare |-> + state_preparation = X.to_circuit() + state_preparation.h(0) + else: # prepare |+> + state_preparation = H.to_circuit() - def test_qpe_Xminus(self): - """eigenproblem X, |->""" - unitary_circuit = X.to_circuit() - state_preparation = X.to_circuit() - state_preparation.append(H.to_circuit(), [0]) # prepare |-> phase = self.one_phase(unitary_circuit, state_preparation) - self.assertEqual(phase, 0.5) + if state == 'minus': + self.assertEqual(phase, 0.5) + else: + self.assertEqual(phase, 0.0) def phase_estimation(self, unitary_circuit, state_preparation=None, num_evaluation_qubits=6, backend=qiskit.BasicAer.get_backend('qasm_simulator')): @@ -258,21 +233,17 @@ def test_qpe_Zplus(self): result = self.phase_estimation( unitary_circuit, state_preparation, backend=qiskit.BasicAer.get_backend('statevector_simulator')) + phases = result.filter_phases(1e-15, as_float=True) with self.subTest('test phases has correct values'): self.assertEqual(list(phases.keys()), [0.0, 0.5]) + with self.subTest('test phases has correct probabilities'): np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) - def test_qpe_Zplus_strings(self): - """superposition eigenproblem Z, |+>, bitstrings""" - unitary_circuit = Z.to_circuit() - state_preparation = H.to_circuit() # prepare |+> - result = self.phase_estimation( - unitary_circuit, state_preparation, - backend=qiskit.BasicAer.get_backend('statevector_simulator')) - phases = result.filter_phases(1e-15, as_float=False) - self.assertEqual(list(phases.keys()), ['000000', '100000']) + with self.subTest('test bitstring representation'): + phases = result.filter_phases(1e-15, as_float=False) + self.assertEqual(list(phases.keys()), ['000000', '100000']) if __name__ == '__main__': From 394564a5d471aabdda62bdc39addaace234dbb4d Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 23 Mar 2021 17:16:39 +0100 Subject: [PATCH 31/37] fix import order --- test/python/algorithms/test_phase_estimator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 6f29b85a7742..52297fb4bbd9 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -13,8 +13,8 @@ """Test phase estimation""" import unittest -from ddt import ddt, data from test.python.algorithms import QiskitAlgorithmsTestCase +from ddt import ddt, data import numpy as np from qiskit.algorithms.phase_estimators import PhaseEstimation, HamiltonianPhaseEstimation from qiskit.opflow.evolutions import PauliTrotterEvolution, MatrixEvolution From 8ce2824ff3b7794103cfd1b8ea2583249fba26a0 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 23 Mar 2021 23:38:53 -0400 Subject: [PATCH 32/37] Improve docstrings for PE and HPE --- .../hamiltonian_phase_estimation.py | 39 +++++++++++++--- .../phase_estimators/phase_estimation.py | 44 +++++++++++++------ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 4c36914a1413..9a0987da67d1 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -29,12 +29,38 @@ class HamiltonianPhaseEstimation: This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimator`, differing only in that the input in that class is a unitary operator, whereas here the input is a Hermitian operator from which a unitary will be obtained by scaling and exponentiating. The scaling is - performed in order to prevent the phases from wrapping around :math:`2\pi`. This class uses and - works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to manage scaling the - Hamiltonian and the phases that are obtained by the QPE algorithm. This includes setting, or - computing, a bound on the eigenvalues of the operator, using this bound to obtain a scale - factor, scaling the operator, and shifting and scaling the measured phases to recover the - eigenvalues. + performed in order to prevent the phases from wrapping around :math:`2\pi`. + The problem of estimating eigenvalues :math:`\lambda_j` of the Hermitian operator + :math:`H` is solved by running a circuit representing + + :math: + + \exp(i b H ) |\psi\rangle = \sum_j \exp(i b \lambda_j ) c_j |\lambda_j\rangle, + + where the input state is + + :math: + + |\psi\rangle = \sum_j c_j |\lambda_j\rangle + + and :math:`\lambda_j` are the eigenvalues of :math:`H`. + + Here, :math:`b` is a scaling factor sufficiently large to map positive :math:`\lambda` to + :math:`[0,\pi)` and negative :math:`\lambda` to :math:`[\pi,2\pi)`. Each time the circuit is + run, one measures a phase corresponding to :math:`lambda_j` with probability :math:`|c_j|^2`. + + If :math:`H` is a Pauli sum, the bound :math`b` is computed from the sum of the absolute + values of the coefficients of the terms. There is no way to reliably recover eigenvalues + from phases very near the endpoints of these intervals. Because of this you should be aware + that for degenerate cases, such as :math:`H=Z`, the eigenvalues :math:`\pm 1` will be + mapped to the same phase, :math`\pi`, and so cannot be distinguished. In this case, you need + to specify a larger bound as an argument to the method `estimate`. + + This class uses and works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to + manage scaling the Hamiltonian and the phases that are obtained by the QPE algorithm. This + includes setting, or computing, a bound on the eigenvalues of the operator, using this + bound to obtain a scale factor, scaling the operator, and shifting and scaling the measured + phases to recover the eigenvalues. Note that, although we speak of "evolving" the state according the the Hamiltonian, in the present algorithm, we are not actually considering time evolution. Rather, the role of time is @@ -48,6 +74,7 @@ class HamiltonianPhaseEstimation: [1]: Quantum phase estimation of multiple eigenvalues for small-scale (noisy) experiments T.E. O'Brien, B. Tarasinski, B.M. Terhal `arXiv:1809.09697 `_ + """ def __init__(self, diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index a7d6bfaec677..f3d01284d158 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -30,23 +30,39 @@ class PhaseEstimation(PhaseEstimator): """Run the Quantum Phase Estimation (QPE) algorithm. - This runs a version of QPE with a multi-qubit register for reading the phase [1]. The main - inputs are the number of qubits in the phase-reading register, a state preparation circuit to - prepare an input state, and either + This runs QPE with a multi-qubit register for reading the phases [1] + of input states. + + The algorithm takes as input a unitary :math:`U` and a state :math:`|\psi\rangle`, + which may be written + :math: + + |\psi\rangle = \sum_j c_j |\phi_j\rangle, + + where :math:`|\phi_j\rangle` are eigenstates of :math:`U`. We prepare the quantum register + in the state :math:`|\psi\rangle` then apply :math:`U` leaving the register in the state + :math: + + U|\psi\rangle = \sum_j \exp(i \phi_j) c_j |\phi_j\rangle. + + In the ideal case, one then measures the phase :math:`\phi_j` with probability + :math:`|c_j|^2`. In practice, many (or all) of the bit strings may be measured due to + noise and the possibility that :math:`\phi_j` may not be representable exactly by the + output register. In the latter case the probability for each eigenphase will be spread + across bitstrings, with amplitudes that decrease with distance from the bitstring most + closely approximating the eigenphase. + + The main inputs are the number of qubits in the phase-reading register, a state preparation + circuit to prepare an input state, and either + 1) A unitary that will act on the the input state, or 2) A quantum-phase-estimation circuit in which the unitary is already embedded. - In case 1), an instance of `qiskit.circuit.PhaseEstimation`, a QPE circuit, containing the input - unitary will be constructed. After construction, the QPE circuit is run on a backend via the - `run` method, and the frequencies or counts of the phases represented by bitstrings are - recorded. The results are returned as an instance of - :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. - If the input state is an eigenstate of the unitary, then in the ideal case, all probability is - concentrated on the bitstring corresponding to the eigenvalue of the input state. If the input - state is a superposition of eigenstates, then each bitstring is measured with a probability - corresponding to its weight in the superposition. In addition, if the phase is not representable - exactly by the phase-reading register, the probability will be spread across bitstrings, with an - amplitude that decreases with distance from the bitstring most closely approximating the phase. + In case 1), an instance of :class:`qiskit.circuit.PhaseEstimation`, a QPE circuit, containing the + input unitary will be constructed. After construction, the QPE circuit is run on a backend + via the `run` method, and the frequencies or counts of the phases represented by bitstrings + are recorded. The results are returned as an instance of + :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. **Reference:** From dbaf678af9a7ee51227cda91ef1383175b70aee2 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 24 Mar 2021 13:24:13 +0100 Subject: [PATCH 33/37] attempt to fix sphinx --- .../hamiltonian_phase_estimation.py | 16 +++++++------- .../hamiltonian_phase_estimation_result.py | 10 ++++----- .../phase_estimators/phase_estimation.py | 21 +++++++++---------- .../phase_estimation_result.py | 6 +++--- .../phase_estimation_scale.py | 14 ++++++------- .../phase_estimators/phase_estimator.py | 4 ++-- 6 files changed, 36 insertions(+), 35 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index 9a0987da67d1..b5990b59c32d 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -26,22 +26,22 @@ class HamiltonianPhaseEstimation: r"""Run the Quantum Phase Estimation algorithm to find the eigenvalues of a Hermitian operator. - This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimator`, differing only + This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimation`, differing only in that the input in that class is a unitary operator, whereas here the input is a Hermitian operator from which a unitary will be obtained by scaling and exponentiating. The scaling is performed in order to prevent the phases from wrapping around :math:`2\pi`. The problem of estimating eigenvalues :math:`\lambda_j` of the Hermitian operator :math:`H` is solved by running a circuit representing - :math: + .. math:: - \exp(i b H ) |\psi\rangle = \sum_j \exp(i b \lambda_j ) c_j |\lambda_j\rangle, + \exp(i b H) |\psi\rangle = \sum_j \exp(i b \lambda_j ) c_j |\lambda_j\rangle, where the input state is - :math: + .. math:: - |\psi\rangle = \sum_j c_j |\lambda_j\rangle + |\psi\rangle = \sum_j c_j |\lambda_j\rangle, and :math:`\lambda_j` are the eigenvalues of :math:`H`. @@ -80,7 +80,8 @@ class HamiltonianPhaseEstimation: def __init__(self, num_evaluation_qubits: int, quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: - """Args: + """ + Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. quantum_instance: The quantum instance on which the circuit will be run. @@ -119,7 +120,8 @@ def estimate(self, hamiltonian: OperatorBase, state_preparation: Optional[StateFn] = None, evolution: Optional[EvolutionBase] = None, bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: - """ + """Run the Hamiltonian phase estimation algorithm. + Args: hamiltonian: a Hermitian operator. state_preparation: The `StateFn` to be prepared, whose eigenphase will be diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 36a32bb34196..08df3fc936c1 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -59,17 +59,17 @@ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, Args: cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. + The default value is `0.0`. scaled: If False, return `phi` in :math:`[0, 1)` rather than the eigenvalues of - the Hamiltonian. + the Hamiltonian. as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. + returned keys are bit strings. Raises: ValueError: if as_float` is `False` and `scaled` is `True`. Returns: - A dict of filtered phases. + A dict of filtered phases. """ if scaled and not as_float: raise ValueError('`as_float` must be `True` if `scaled` is `True`.') @@ -83,7 +83,7 @@ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, @property def most_likely_phase(self) -> float: - """The most likely phase of the unitary corresponding to the Hamiltonian. + """The most likely phase of the unitary corresponding to the Hamiltonian. Returns: The most likely phase. diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index f3d01284d158..1d4e542c6b10 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -28,20 +28,22 @@ class PhaseEstimation(PhaseEstimator): - """Run the Quantum Phase Estimation (QPE) algorithm. + r"""Run the Quantum Phase Estimation (QPE) algorithm. This runs QPE with a multi-qubit register for reading the phases [1] of input states. The algorithm takes as input a unitary :math:`U` and a state :math:`|\psi\rangle`, which may be written - :math: + + .. math:: |\psi\rangle = \sum_j c_j |\phi_j\rangle, where :math:`|\phi_j\rangle` are eigenstates of :math:`U`. We prepare the quantum register in the state :math:`|\psi\rangle` then apply :math:`U` leaving the register in the state - :math: + + .. math:: U|\psi\rangle = \sum_j \exp(i \phi_j) c_j |\phi_j\rangle. @@ -58,8 +60,8 @@ class PhaseEstimation(PhaseEstimator): 1) A unitary that will act on the the input state, or 2) A quantum-phase-estimation circuit in which the unitary is already embedded. - In case 1), an instance of :class:`qiskit.circuit.PhaseEstimation`, a QPE circuit, containing the - input unitary will be constructed. After construction, the QPE circuit is run on a backend + In case 1), an instance of :class:`qiskit.circuit.PhaseEstimation`, a QPE circuit, containing + the input unitary will be constructed. After construction, the QPE circuit is run on a backend via the `run` method, and the frequencies or counts of the phases represented by bitstrings are recorded. The results are returned as an instance of :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. @@ -76,14 +78,11 @@ def __init__(self, num_evaluation_qubits: int, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None) -> None: - """Args: + """ + Args: num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will be estimated as a binary string with this many bits. quantum_instance: The quantum instance on which the circuit will be run. - - Raises: # TODO: fix this while refactoring - ValueError: unless only one of `unitary` and `pe_circuit` is `None`. - `num_unitary_qubits` disagrees with size of `unitary`. """ self._measurements_added = False @@ -204,7 +203,7 @@ def estimate(self, ValueError: If neither `pe_circuit` nor `unitary` are passed. Returns: - An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. + An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. """ num_evaluation_qubits = self._num_evaluation_qubits diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py index 8416c6353fae..0322fc24e94f 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_result.py @@ -90,9 +90,9 @@ def filter_phases(self, cutoff: float = 0.0, Args: cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. + The default value is `0.0`. as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. + returned keys are bit strings. Returns: A filtered dict of phases (keys) and frequencies (values). @@ -136,7 +136,7 @@ def _bit_string_to_phase(binary_string: str) -> float: binary_string: A string of characters '0' and '1'. Returns: - a phase scaled to :math:`[0,1)`. + A phase scaled to :math:`[0,1)`. """ n_qubits = len(binary_string) return int(binary_string, 2) / (2 ** n_qubits) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index 7c9dc98a80f0..b9dc624c2ec5 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -43,8 +43,8 @@ class PhaseEstimationScale(): def __init__(self, bound: float) -> None: """ Args: - bound: an upper bound on the absolute value of the - eigenvalues of a Hermitian operator. (The operator is not needed here.) + bound: an upper bound on the absolute value of the eigenvalues of a Hermitian operator. + (The operator is not needed here.) """ self._bound = bound @@ -78,8 +78,8 @@ def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: operator. Args: - phi: Normalized phase in :math:`[0, 1)` to be converted to an eigenvalue. - id_coefficient: All eigenvalues are shifted by this value. + phi: Normalized phase in :math:`[0, 1)` to be converted to an eigenvalue. + id_coefficient: All eigenvalues are shifted by this value. Returns: An eigenvalue computed from the input phase. @@ -123,13 +123,13 @@ def from_pauli_sum(cls, pauli_sum: SummedOp) -> 'PhaseEstimationScale': the generic case. A `PhaseEstimationScale` object is instantiated using this bound. Args: - pauli_sum: A `SummedOp` whose terms are `PauliOp`s. + pauli_sum: A `SummedOp` whose terms are `PauliOp`s. Raises: - ValueError: if `pauli_sum` is not a sum of Pauli operators.' + ValueError: if `pauli_sum` is not a sum of Pauli operators.' Returns: - A `PhaseEstimationScale` object + A `PhaseEstimationScale` object """ if pauli_sum.primitive_strings() != {'Pauli'}: raise ValueError( diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py index bd492316e971..31af0b3ad976 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ b/qiskit/algorithms/phase_estimators/phase_estimator.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The Phase Estimator interface""" +"""The Phase Estimator interface.""" from typing import Optional from abc import ABC, abstractmethod, abstractproperty @@ -19,7 +19,7 @@ class PhaseEstimator(ABC): - """The Phase Estimator interface + """The Phase Estimator interface. Algorithms that can compute a phase for a unitary operator and initial state may implement this interface to allow different From 661cdc870f9fed61deba0276b840fcfe9a3f0fe4 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 24 Mar 2021 14:25:00 +0100 Subject: [PATCH 34/37] attempt to fix sphinx #2 --- .../hamiltonian_phase_estimation.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py index b5990b59c32d..1a3e40273051 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py @@ -35,7 +35,7 @@ class HamiltonianPhaseEstimation: .. math:: - \exp(i b H) |\psi\rangle = \sum_j \exp(i b \lambda_j ) c_j |\lambda_j\rangle, + \exp(i b H) |\psi\rangle = \sum_j \exp(i b \lambda_j) c_j |\lambda_j\rangle, where the input state is @@ -49,12 +49,12 @@ class HamiltonianPhaseEstimation: :math:`[0,\pi)` and negative :math:`\lambda` to :math:`[\pi,2\pi)`. Each time the circuit is run, one measures a phase corresponding to :math:`lambda_j` with probability :math:`|c_j|^2`. - If :math:`H` is a Pauli sum, the bound :math`b` is computed from the sum of the absolute + If :math:`H` is a Pauli sum, the bound :math:`b` is computed from the sum of the absolute values of the coefficients of the terms. There is no way to reliably recover eigenvalues from phases very near the endpoints of these intervals. Because of this you should be aware that for degenerate cases, such as :math:`H=Z`, the eigenvalues :math:`\pm 1` will be - mapped to the same phase, :math`\pi`, and so cannot be distinguished. In this case, you need - to specify a larger bound as an argument to the method `estimate`. + mapped to the same phase, :math:`\pi`, and so cannot be distinguished. In this case, you need + to specify a larger bound as an argument to the method ``estimate``. This class uses and works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to manage scaling the Hamiltonian and the phases that are obtained by the QPE algorithm. This @@ -123,16 +123,16 @@ def estimate(self, hamiltonian: OperatorBase, """Run the Hamiltonian phase estimation algorithm. Args: - hamiltonian: a Hermitian operator. - state_preparation: The `StateFn` to be prepared, whose eigenphase will be + hamiltonian: A Hermitian operator. + state_preparation: The ``StateFn`` to be prepared, 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. - evolution: An evolution object that generates a unitary from `hamiltonian`. If - `None`, then the default `PauliTrotterEvolution` is used. + evolution: An evolution converter that generates a unitary from ``hamiltonian``. If + ``None``, then the default ``PauliTrotterEvolution`` is used. bound: An upper bound on the absolute value of the eigenvalues of - `hamiltonian`. If omitted, then `hamiltonian` must be a Pauli sum, or a - PauliOp, in which case a bound will be computed. If `hamiltonian` - is a `MatrixOp`, then `bound` may not be `None`. The tighter the bound, + ``hamiltonian``. If omitted, then ``hamiltonian`` must be a Pauli sum, or a + ``PauliOp``, in which case a bound will be computed. If ``hamiltonian`` + is a ``MatrixOp``, then ``bound`` may not be ``None``. The tighter the bound, the higher the resolution of computed phases. Returns: @@ -140,9 +140,9 @@ def estimate(self, hamiltonian: OperatorBase, and diagnostic information. Raises: - ValueError: if `bound` is `None` and `hamiltonian` is not a Pauli sum (i.e. a - `PauliSumOp` or a `SummedOp` whose terms are `PauliOp`s.) - TypeError: if `evolution` is not of type `EvolutionBase`. + ValueError: If ``bound`` is ``None`` and ``hamiltonian`` is not a Pauli sum, i.e. a + ``PauliSumOp`` or a ``SummedOp`` whose terms are of type ``PauliOp``. + TypeError: If ``evolution`` is not of type ``EvolutionBase``. """ if evolution is None: evolution = PauliTrotterEvolution() From 7d2c55647320530bdee630ebd8ddc150842325a2 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 24 Mar 2021 17:36:12 +0100 Subject: [PATCH 35/37] attempt no. 3 to fix sphinx --- .../phase_estimation_scale.py | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py index b9dc624c2ec5..d3509e4d56cf 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py @@ -21,16 +21,17 @@ class PhaseEstimationScale(): """Set and use a bound on eigenvalues of a Hermitian operator in order to ensure phases are in the desired range and to convert measured phases into eigenvectors. - The `bound` is set when constructing this class. Then the method `scale` is used to find the + The ``bound`` is set when constructing this class. Then the method ``scale`` is used to find the factor by which to scale the operator. - If `bound` is equal exactly to the largest eigenvalue, and the smallest eigenvalue is minus the - largest, then these two eigenvalues will not be distinguished. For example, if the Hermitian - operator is the Pauli `Z operator with eigenvalues `+1` and `-1`, and `bound` is `1`, then both - eigenvalues will be mapped to `1`. This can be avoided by making `bound` a bit larger. + If ``bound`` is equal exactly to the largest eigenvalue, and the smallest eigenvalue is minus + the largest, then these two eigenvalues will not be distinguished. For example, if the Hermitian + operator is the Pauli Z operator with eigenvalues :math:`1` and :math:`-1`, and ``bound`` is + :math:`1`, then both eigenvalues will be mapped to :math:`1`. + This can be avoided by making ``bound`` a bit larger. - Increasing `bound` decreases the part of the interval :math:`[0, 1)` that is used to map - eigenvalues to `phi`. However, sometimes this results in a better determination of the + Increasing ``bound`` decreases the part of the interval :math:`[0, 1)` that is used to map + eigenvalues to ``phi``. However, sometimes this results in a better determination of the eigenvalues, because 1) although there are fewer discrete phases in the useful range, it may shift one of the discrete phases closer to the actual phase. And, 2) If one of the discrete phases is close to, or exactly equal to the actual phase, then artifacts (probability) in @@ -55,7 +56,7 @@ def scale(self) -> float: Return the scale factor by which a Hermitian operator must be multiplied so that the phase of the corresponding unitary is restricted to :math:`[-\pi, \pi]`. This factor is computed from the bound on the absolute values of the eigenvalues - of the operator. The methods `scale_phase` and `scale_phases` are used recover + of the operator. The methods ``scale_phase`` and ``scale_phases`` are used recover the eigenvalues corresponding the original (unscaled) Hermitian operator. Returns: @@ -66,13 +67,13 @@ def scale(self) -> float: def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: r"""Convert a phase into an eigenvalue. - The input phase `phi` corresponds to the eigenvalue of a unitary obtained by + The input phase ``phi`` corresponds to the eigenvalue of a unitary obtained by exponentiating a scaled Hermitian operator. Recall that the phase - is obtained from `phi` as :math:`2\pi\phi`. Furthermore, the Hermitian operator - was scaled so that `phi` is restricted to :math:`[-1/2, 1/2]`, corresponding to + is obtained from ``phi`` as :math:`2\pi\phi`. Furthermore, the Hermitian operator + was scaled so that ``phi`` is restricted to :math:`[-1/2, 1/2]`, corresponding to phases in :math:`[-\pi, \pi]`. But the values of `phi` read from the phase-readout - register are in :math:`[0, 1)`. Any value of `phi` greater than `1/2` corresponds - to a raw phase of minus the complement with respect to `1`. After this possible + register are in :math:`[0, 1)`. Any value of ``phi`` greater than :math:`1/2` corresponds + to a raw phase of minus the complement with respect to 1. After this possible shift, the phase is scaled by the inverse of the factor by which the Hermitian operator was scaled to recover the eigenvalue of the Hermitian operator. @@ -95,12 +96,12 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 ) -> Union[Dict, List]: """Convert a list or dict of phases to eigenvalues. - The values in the list, or keys in the dict, are values of `phi` and - are converted as described in the description of `scale_phase`. In case - `phases` is a dict, the values of the dict are passed unchanged. + The values in the list, or keys in the dict, are values of ``phi` and + are converted as described in the description of ``scale_phase``. In case + ``phases`` is a dict, the values of the dict are passed unchanged. Args: - phases: a list or dict of values of `phi`. + phases: a list or dict of values of ``phi``. id_coefficient: All eigenvalues are shifted by this value. Returns: @@ -117,19 +118,19 @@ def scale_phases(self, phases: Union[List, Dict], id_coefficient: float = 0.0 def from_pauli_sum(cls, pauli_sum: SummedOp) -> 'PhaseEstimationScale': """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. - It is assumed that the `SummedOp` `pauli_sum` is the sum of `PauliOp`s. The bound on + It is assumed that the ``pauli_sum`` is the sum of ``PauliOp`` objects. The bound on the absolute value of the eigenvalues of the sum is obtained as the sum of the absolute values of the coefficients of the terms. This is the best bound available in - the generic case. A `PhaseEstimationScale` object is instantiated using this bound. + the generic case. A ``PhaseEstimationScale`` object is instantiated using this bound. Args: - pauli_sum: A `SummedOp` whose terms are `PauliOp`s. + pauli_sum: A ``SummedOp`` whose terms are ``PauliOp`` objects. Raises: - ValueError: if `pauli_sum` is not a sum of Pauli operators.' + ValueError: if ``pauli_sum`` is not a sum of Pauli operators. Returns: - A `PhaseEstimationScale` object + A ``PhaseEstimationScale`` object """ if pauli_sum.primitive_strings() != {'Pauli'}: raise ValueError( From 328508c4e03046e21c794dd6f73d0cec6f78e8d4 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 25 Mar 2021 09:43:23 +0100 Subject: [PATCH 36/37] layout, don't use function calls in defaults --- qiskit/algorithms/__init__.py | 2 +- .../hamiltonian_phase_estimation_result.py | 2 +- test/python/algorithms/test_phase_estimator.py | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index c77b75bdd4e2..d6333da36758 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -117,7 +117,7 @@ VQE Phase Estimators -++++++++++++++++++++ +++++++++++++++++ Algorithms that estimate the phases of eigenstates of a unitary. .. autosummary:: diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py index 08df3fc936c1..f2012780895a 100644 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py @@ -66,7 +66,7 @@ def filter_phases(self, cutoff: float = 0.0, scaled: bool = True, returned keys are bit strings. Raises: - ValueError: if as_float` is `False` and `scaled` is `True`. + ValueError: if `as_float` is `False` and `scaled` is `True`. Returns: A dict of filtered phases. diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index 52297fb4bbd9..e42f39aabe2e 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -27,10 +27,12 @@ class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): """Tests for obtaining eigenvalues from phase estimation""" def hamiltonian_pe(self, hamiltonian, state_preparation=None, num_evaluation_qubits=6, - backend=qiskit.BasicAer.get_backend('statevector_simulator'), + backend=None, evolution=None, bound=None): """Run HamiltonianPhaseEstimation and return result with all phases.""" + if backend is None: + backend = qiskit.BasicAer.get_backend('statevector_simulator') quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = HamiltonianPhaseEstimation( num_evaluation_qubits=num_evaluation_qubits, @@ -215,10 +217,12 @@ def test_qpe_Xplus(self, state): self.assertEqual(phase, 0.0) def phase_estimation(self, unitary_circuit, state_preparation=None, num_evaluation_qubits=6, - backend=qiskit.BasicAer.get_backend('qasm_simulator')): + backend=None): """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, `state_preparation`. Return all results """ + if backend is None: + backend = qiskit.BasicAer.get_backend('statevector_simulator'), qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, quantum_instance=qi) From 7056d3585c6fabca55af54232d810f677c677c89 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 25 Mar 2021 10:03:44 +0100 Subject: [PATCH 37/37] trailing comma --- test/python/algorithms/test_phase_estimator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py index e42f39aabe2e..4cb48bf34a46 100644 --- a/test/python/algorithms/test_phase_estimator.py +++ b/test/python/algorithms/test_phase_estimator.py @@ -222,7 +222,7 @@ def phase_estimation(self, unitary_circuit, state_preparation=None, num_evaluati `state_preparation`. Return all results """ if backend is None: - backend = qiskit.BasicAer.get_backend('statevector_simulator'), + backend = qiskit.BasicAer.get_backend('statevector_simulator') qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, quantum_instance=qi)