Trotterization-based Quantum Real Time Evolution (#7411)

* Introduced time evolution classes and interfaces.

* Migrated trotterization as trotterization builder.

* Implemented enum for trotterization mode; fixed imports.

* Implemented trotter_qrte.py with unit tests.

* trotter_qrte.py repetitions added.

* Code refactoring.

* Code refactoring.

* Gradient object introduced in a signature.

* Draft of trotter_qrte.py gradient with unit tests.

* trotter_qrte.py improvements; unit tests extended.

* trotter_qrte.py improvements; unit tests extended.

* Trotter test fix.

* Fixed float and complex handling.

* qrte with product formula

* Removed irrelevant files.

* Removed irrelevant files.

* Extended docs.

* Removed outdated unit tests.

* Removed outdated files.

* Code refactoring, docs extended.

* Code refactoring.

* Updated unit tests.

* Code refactoring, extended docs and typehints.

* Reno added.

* Extended checks and unit tests.

* Code refactoring.

* Code refactoring.

* fix problem with test

* Docs extended.

* Added support for PauliOp, code refactoring.

* Implemented general Quantum Time Evolution Framework interfaces.

* Updated docs.

* Reno added.

* Improved reno.

* Code refactoring.

* Update qiskit/algorithms/time_evolution/evolution_base.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Code refactoring.

* Introduced evolution problem classes.

* Code refactoring.

* Apply suggestions from code review

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Added unit tests.

* Lint fixed.

* Code refactoring

* Code refactoring

* Code refactoring

* Code refactoring

* Code refactoring

* Code refactoring

* Code review changes.

* Removed gradient code for now.

* Code review refactoring.

* Removed gradient code for now.

* Evolving observable removed. Evaluating observables added.

* Code refactoring.

* Improved naming.

* Improved folder structure; filled evolvers init file.

* Added Evolvers to algorithms init file.

* Fixed missing imports.

* Code refactoring

* Fixed cyclic imports.

* Extracted ListOrDict.

* Code refactoring.

* Code refactoring.

* Fixed release note.

* Fixed inheritance order.

* Updated code to the latest time evolution interface.

* Code refactoring.

* Code refactoring.

* Code refactoring.

* Fixed cyclic imports.

* Fixed pylint.

* Name fix.

* Import fix.

* Lint fix.

* Lint fix.

* Code refactoring.

* Extracted and refactored aux_ops_evaluator.py

* Code refactoring

* Implemented unit test.

* Extended unit test.

* Date fixed.

* Reno added.

* Switched to bound ansatz.

* Fix reno.

* Added docs.

* Refactored unit test.

* Lint fixed.

* Code review edits.

* Added unit test cases for dicts.

* Fixed reno reference.

* Improved unit test.

* Added quantum instance support.

* Added support for aux_ops and unit test.

* Fixed input object to eval_observables.

* Fixed quantum_state types and conversion.

* Fixed quantum_state types and conversion.

* Fixed types and docs.

* Code refactoring.

* Lint fix.

* Code refactoring.

* Reno update.

* Code refactoring.

* Implemented some CR feedback.

* evolution_problem.py fix

* Removed trotter_ops_validator.py for now, extended unit tests, code refactoring.

* Updated names of the algorithm.

* Added support for QuantumCircuit input. Code refactoring.

* Improved error handling in evolution_problem.py and unit tests added.

* Code refactoring.

* Cyclic import fix

* Refactored evolution_problem.py

* Refactored test_trotter_qrte.py

* Removed global tolerance.

* CI fix.

* Code review fixes.

* Code refactoring.

* Prepared qasm unit test.

* Removing state eval.

* Refactored and updated tests.

* Added qasm test for aux ops.

* Added param binding test.

* Added missing setters.

* Updated unit tests.

* Improved folder structure.

* Updated interfaces.

* Fix lint.

* Removed legacy (soon) BaseBackend.

* Updated copyright years.

* Added example to reno.

* Fixed reno.

* Removed BaseOperator.

* Code refactoring.

* Imports fix.

* Delayed parameters checks.

* Update qiskit/algorithms/evolvers/evolution_problem.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Code refactoring.

* Update qiskit/algorithms/evolvers/evolution_problem.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/algorithms/evolvers/trotterization/trotter_qrte.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Expectation might be None

Co-authored-by: acv@zurich.ibm.com <acv@zurich.ibm.com>
Co-authored-by: Julien Gacon <gaconju@gmail.com>
This commit is contained in:
dlasecki 2022-04-27 10:26:14 +02:00 committed by GitHub
parent 7e83de6d80
commit 18b5a048ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 643 additions and 18 deletions

View File

@ -106,6 +106,7 @@ used to train Quantum Boltzmann Machine Neural Networks for example.
RealEvolver
ImaginaryEvolver
TrotterQRTE
EvolutionResult
EvolutionProblem
@ -205,8 +206,8 @@ Utility methods used by algorithms.
from .algorithm_result import AlgorithmResult
from .evolvers import EvolutionResult, EvolutionProblem
from .evolvers.real.real_evolver import RealEvolver
from .evolvers.imaginary.imaginary_evolver import ImaginaryEvolver
from .evolvers.real_evolver import RealEvolver
from .evolvers.imaginary_evolver import ImaginaryEvolver
from .variational_algorithm import VariationalAlgorithm, VariationalResult
from .amplitude_amplifiers import Grover, GroverResult, AmplificationProblem, AmplitudeAmplifier
from .amplitude_estimators import (
@ -243,6 +244,7 @@ from .phase_estimators import (
)
from .exceptions import AlgorithmError
from .aux_ops_evaluator import eval_observables
from .evolvers.trotterization import TrotterQRTE
__all__ = [
"AlgorithmResult",
@ -266,6 +268,7 @@ __all__ = [
"NumPyEigensolver",
"RealEvolver",
"ImaginaryEvolver",
"TrotterQRTE",
"EvolutionResult",
"EvolutionProblem",
"LinearSolverResult",

View File

@ -23,9 +23,8 @@ from ..list_or_dict import ListOrDict
class EvolutionProblem:
"""Evolution problem class.
This class is the input to time evolution algorithms and contains
information on e.g. the total evolution time and under which Hamiltonian
the state is evolved.
This class is the input to time evolution algorithms and must contain information on the total
evolution time, a quantum state to be evolved and under which Hamiltonian the state is evolved.
"""
def __init__(
@ -34,8 +33,9 @@ class EvolutionProblem:
time: float,
initial_state: Union[StateFn, QuantumCircuit],
aux_operators: Optional[ListOrDict[OperatorBase]] = None,
truncation_threshold: float = 1e-12,
t_param: Optional[Parameter] = None,
hamiltonian_value_dict: Optional[Dict[Parameter, Union[complex]]] = None,
hamiltonian_value_dict: Optional[Dict[Parameter, complex]] = None,
):
"""
Args:
@ -44,15 +44,64 @@ class EvolutionProblem:
initial_state: Quantum state to be evolved.
aux_operators: Optional list of auxiliary operators to be evaluated with the
evolved ``initial_state`` and their expectation values returned.
truncation_threshold: Defines a threshold under which values can be assumed to be 0.
Used when ``aux_operators`` is provided.
t_param: Time parameter in case of a time-dependent Hamiltonian. This
free parameter must be within the ``hamiltonian``.
hamiltonian_value_dict: If the Hamiltonian contains free parameters, this
dictionary maps all these parameters to values.
Raises:
ValueError: If non-positive time of evolution is provided.
"""
self.t_param = t_param
self.hamiltonian_value_dict = hamiltonian_value_dict
self.hamiltonian = hamiltonian
self.time = time
self.initial_state = initial_state
self.aux_operators = aux_operators
self.t_param = t_param
self.hamiltonian_value_dict = hamiltonian_value_dict
self.truncation_threshold = truncation_threshold
@property
def time(self) -> float:
"""Returns time."""
return self._time
@time.setter
def time(self, time: float) -> None:
"""
Sets time and validates it.
Raises:
ValueError: If time is not positive.
"""
if time <= 0:
raise ValueError(f"Evolution time must be > 0 but was {time}.")
self._time = time
def validate_params(self) -> None:
"""
Checks if all parameters present in the Hamiltonian are also present in the dictionary
that maps them to values.
Raises:
ValueError: If Hamiltonian parameters cannot be bound with data provided.
"""
if isinstance(self.hamiltonian, OperatorBase):
t_param_set = set()
if self.t_param is not None:
t_param_set.add(self.t_param)
hamiltonian_dict_param_set = set()
if self.hamiltonian_value_dict is not None:
hamiltonian_dict_param_set = hamiltonian_dict_param_set.union(
set(self.hamiltonian_value_dict.keys())
)
params_set = t_param_set.union(hamiltonian_dict_param_set)
hamiltonian_param_set = set(self.hamiltonian.parameters)
if hamiltonian_param_set != params_set:
raise ValueError(
f"Provided parameters {params_set} do not match Hamiltonian parameters "
f"{hamiltonian_param_set}."
)

View File

@ -16,7 +16,7 @@ from typing import Optional, Union, Tuple
from qiskit import QuantumCircuit
from qiskit.algorithms.list_or_dict import ListOrDict
from qiskit.opflow import StateFn
from qiskit.opflow import StateFn, OperatorBase
from ..algorithm_result import AlgorithmResult
@ -25,7 +25,7 @@ class EvolutionResult(AlgorithmResult):
def __init__(
self,
evolved_state: Union[StateFn, QuantumCircuit],
evolved_state: Union[StateFn, QuantumCircuit, OperatorBase],
aux_ops_evaluated: Optional[ListOrDict[Tuple[complex, complex]]] = None,
):
"""

View File

@ -14,8 +14,8 @@
from abc import ABC, abstractmethod
from ..evolution_problem import EvolutionProblem
from ..evolution_result import EvolutionResult
from .evolution_problem import EvolutionProblem
from .evolution_result import EvolutionResult
class ImaginaryEvolver(ABC):

View File

@ -14,8 +14,8 @@
from abc import ABC, abstractmethod
from ..evolution_problem import EvolutionProblem
from ..evolution_result import EvolutionResult
from .evolution_problem import EvolutionProblem
from .evolution_result import EvolutionResult
class RealEvolver(ABC):

View File

@ -9,3 +9,13 @@
# 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.
"""This package contains Trotterization-based Quantum Real Time Evolution algorithm.
It is compliant with the new Quantum Time Evolution Framework and makes use of
:class:`qiskit.synthesis.evolution.ProductFormula` and
:class:`~qiskit.circuit.library.PauliEvolutionGate` implementations. """
from qiskit.algorithms.evolvers.trotterization.trotter_qrte import (
TrotterQRTE,
)
__all__ = ["TrotterQRTE"]

View File

@ -0,0 +1,245 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
#
# 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.
"""An algorithm to implement a Trotterization real time-evolution."""
from typing import Union, Optional
from qiskit import QuantumCircuit
from qiskit.algorithms.aux_ops_evaluator import eval_observables
from qiskit.algorithms.evolvers import EvolutionProblem, EvolutionResult
from qiskit.algorithms.evolvers.real_evolver import RealEvolver
from qiskit.opflow import (
SummedOp,
PauliOp,
CircuitOp,
ExpectationBase,
CircuitSampler,
PauliSumOp,
StateFn,
OperatorBase,
)
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.providers import Backend
from qiskit.synthesis import ProductFormula, LieTrotter
from qiskit.utils import QuantumInstance
class TrotterQRTE(RealEvolver):
"""Quantum Real Time Evolution using Trotterization.
Type of Trotterization is defined by a ProductFormula provided.
Examples:
.. jupyter-execute::
from qiskit.opflow import X, Z, Zero
from qiskit.algorithms import EvolutionProblem, TrotterQRTE
from qiskit import BasicAer
from qiskit.utils import QuantumInstance
operator = X + Z
initial_state = Zero
time = 1
evolution_problem = EvolutionProblem(operator, 1, initial_state)
# LieTrotter with 1 rep
backend = BasicAer.get_backend("statevector_simulator")
quantum_instance = QuantumInstance(backend=backend)
trotter_qrte = TrotterQRTE(quantum_instance=quantum_instance)
evolved_state = trotter_qrte.evolve(evolution_problem).evolved_state
"""
def __init__(
self,
product_formula: Optional[ProductFormula] = None,
expectation: Optional[ExpectationBase] = None,
quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
) -> None:
"""
Args:
product_formula: A Lie-Trotter-Suzuki product formula. The default is the Lie-Trotter
first order product formula with a single repetition.
expectation: An instance of ExpectationBase which defines a method for calculating
expectation values of EvolutionProblem.aux_operators.
quantum_instance: A quantum instance used for calculating expectation values of
EvolutionProblem.aux_operators.
"""
if product_formula is None:
product_formula = LieTrotter()
self._product_formula = product_formula
self._quantum_instance = None
self._circuit_sampler = None
if quantum_instance is not None:
self.quantum_instance = quantum_instance
self._expectation = expectation
@property
def product_formula(self) -> ProductFormula:
"""Returns a product formula used in the algorithm."""
return self._product_formula
@product_formula.setter
def product_formula(self, product_formula: ProductFormula) -> None:
"""
Sets a product formula.
Args:
product_formula: A formula that defines the Trotterization algorithm.
"""
self._product_formula = product_formula
@property
def quantum_instance(self) -> Optional[QuantumInstance]:
"""Returns a quantum instance used in the algorithm."""
return self._quantum_instance
@quantum_instance.setter
def quantum_instance(self, quantum_instance: Optional[Union[QuantumInstance, Backend]]) -> None:
"""
Sets a quantum instance and a circuit sampler.
Args:
quantum_instance: The quantum instance used to run this algorithm.
"""
if isinstance(quantum_instance, Backend):
quantum_instance = QuantumInstance(quantum_instance)
self._circuit_sampler = None
if quantum_instance is not None:
self._circuit_sampler = CircuitSampler(quantum_instance)
self._quantum_instance = quantum_instance
@property
def expectation(self) -> Optional[ExpectationBase]:
"""Returns an expectation used in the algorithm."""
return self._expectation
@expectation.setter
def expectation(self, expectation: Optional[ExpectationBase]) -> None:
"""
Sets an expectation.
Args:
expectation: An instance of ExpectationBase which defines a method for calculating
expectation values of EvolutionProblem.aux_operators.
"""
self._expectation = expectation
@classmethod
def supports_aux_operators(cls) -> bool:
"""
Whether computing the expectation value of auxiliary operators is supported.
Returns:
True if ``aux_operators`` expectations in the EvolutionProblem can be evaluated, False
otherwise.
"""
return True
def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult:
"""
Evolves a quantum state for a given time using the Trotterization method
based on a product formula provided. The result is provided in the form of a quantum
circuit. If auxiliary operators are included in the ``evolution_problem``, they are
evaluated on an evolved state using a backend provided.
.. note::
Time-dependent Hamiltonians are not yet supported.
Args:
evolution_problem: Instance defining evolution problem. For the included Hamiltonian,
``PauliOp``, ``SummedOp`` or ``PauliSumOp`` are supported by TrotterQRTE.
Returns:
Evolution result that includes an evolved state as a quantum circuit and, optionally,
auxiliary operators evaluated for a resulting state on a backend.
Raises:
ValueError: If ``t_param`` is not set to None in the EvolutionProblem (feature not
currently supported).
ValueError: If the ``initial_state`` is not provided in the EvolutionProblem.
"""
evolution_problem.validate_params()
if evolution_problem.t_param is not None:
raise ValueError(
"TrotterQRTE does not accept a time dependent hamiltonian,"
"``t_param`` from the EvolutionProblem should be set to None."
)
if evolution_problem.aux_operators is not None and (
self._quantum_instance is None or self._expectation is None
):
raise ValueError(
"aux_operators were provided for evaluations but no ``expectation`` or "
"``quantum_instance`` was provided."
)
hamiltonian = evolution_problem.hamiltonian
if not isinstance(hamiltonian, (PauliOp, PauliSumOp, SummedOp)):
raise ValueError(
"TrotterQRTE only accepts PauliOp | "
f"PauliSumOp | SummedOp, {type(hamiltonian)} provided."
)
if isinstance(hamiltonian, OperatorBase):
hamiltonian = hamiltonian.bind_parameters(evolution_problem.hamiltonian_value_dict)
if isinstance(hamiltonian, SummedOp):
hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian)
# the evolution gate
evolution_gate = CircuitOp(
PauliEvolutionGate(hamiltonian, evolution_problem.time, synthesis=self._product_formula)
)
if evolution_problem.initial_state is not None:
initial_state = evolution_problem.initial_state
if isinstance(initial_state, QuantumCircuit):
initial_state = StateFn(initial_state)
evolved_state = evolution_gate @ initial_state
else:
raise ValueError("``initial_state`` must be provided in the EvolutionProblem.")
evaluated_aux_ops = None
if evolution_problem.aux_operators is not None:
evaluated_aux_ops = eval_observables(
self._quantum_instance,
evolved_state.primitive,
evolution_problem.aux_operators,
self._expectation,
evolution_problem.truncation_threshold,
)
return EvolutionResult(evolved_state, evaluated_aux_ops)
@staticmethod
def _summed_op_to_pauli_sum_op(
hamiltonian: SummedOp,
) -> Union[PauliSumOp, PauliOp]:
"""
Tries binding parameters in a Hamiltonian.
Args:
hamiltonian: The Hamiltonian that defines an evolution.
Returns:
Hamiltonian.
Raises:
ValueError: If the ``SummedOp`` Hamiltonian contains operators of an invalid type.
"""
# PauliSumOp does not allow parametrized coefficients but after binding the parameters
# we need to convert it into a PauliSumOp for the PauliEvolutionGate.
op_list = []
for op in hamiltonian.oplist:
if not isinstance(op, PauliOp):
raise ValueError(
"Content of the Hamiltonian not of type PauliOp. The "
f"following type detected: {type(op)}."
)
op_list.append(op)
return sum(op_list)

View File

@ -0,0 +1,28 @@
---
features:
- |
Added Trotterization-based Quantum Real Time Evolution Algorithm
:class:`qiskit.algorithms.TrotterQRTE`. It is compliant with the new Quantum Time Evolution
Framework and makes use of :class:`qiskit.synthesis.evolution.ProductFormula` and
:class:`qiskit.circuit.library.PauliEvolutionGate` implementations.
.. code-block:: python
from qiskit.algorithms import EvolutionProblem
from qiskit.algorithms.evolvers.trotterization import (
TrotterQRTE,
)
from qiskit.opflow import (
X,
Z,
StateFn,
SummedOp,
)
operator = SummedOp([X, Z])
initial_state = StateFn([1, 0])
time = 1
evolution_problem = EvolutionProblem(operator, time, initial_state)
trotter_qrte = TrotterQRTE()
evolution_result = trotter_qrte.evolve(evolution_problem)
evolved_state_circuit = evolution_result.evolved_state

View File

@ -12,13 +12,16 @@
"""Test evolver problem class."""
import unittest
from test.python.algorithms import QiskitAlgorithmsTestCase
from ddt import data, ddt, unpack
from numpy.testing import assert_raises
from qiskit.algorithms.evolvers.evolution_problem import EvolutionProblem
from qiskit.circuit import Parameter
from qiskit.opflow import Y, Z, One, X
from qiskit.opflow import Y, Z, One, X, Zero
@ddt
class TestEvolutionProblem(QiskitAlgorithmsTestCase):
"""Test evolver problem class."""
@ -54,7 +57,12 @@ class TestEvolutionProblem(QiskitAlgorithmsTestCase):
hamiltonian_value_dict = {t_parameter: 3.2}
evo_problem = EvolutionProblem(
hamiltonian, time, initial_state, aux_operators, t_parameter, hamiltonian_value_dict
hamiltonian,
time,
initial_state,
aux_operators,
t_param=t_parameter,
hamiltonian_value_dict=hamiltonian_value_dict,
)
expected_hamiltonian = Y + t_parameter * Z
@ -71,6 +79,44 @@ class TestEvolutionProblem(QiskitAlgorithmsTestCase):
self.assertEqual(evo_problem.t_param, expected_t_param)
self.assertEqual(evo_problem.hamiltonian_value_dict, expected_hamiltonian_value_dict)
@data([Y, -1, One], [Y, -1.2, One], [Y, 0, One])
@unpack
def test_init_errors(self, hamiltonian, time, initial_state):
"""Tests expected errors are thrown on invalid time argument."""
with assert_raises(ValueError):
_ = EvolutionProblem(hamiltonian, time, initial_state)
def test_validate_params(self):
"""Tests expected errors are thrown on parameters mismatch."""
param_x = Parameter("x")
param_y = Parameter("y")
with self.subTest(msg="Parameter missing in dict."):
hamiltonian = param_x * X + param_y * Y
param_dict = {param_y: 2}
evolution_problem = EvolutionProblem(
hamiltonian, 2, Zero, hamiltonian_value_dict=param_dict
)
with assert_raises(ValueError):
evolution_problem.validate_params()
with self.subTest(msg="Empty dict."):
hamiltonian = param_x * X + param_y * Y
param_dict = {}
evolution_problem = EvolutionProblem(
hamiltonian, 2, Zero, hamiltonian_value_dict=param_dict
)
with assert_raises(ValueError):
evolution_problem.validate_params()
with self.subTest(msg="Extra parameter in dict."):
hamiltonian = param_x * X + param_y * Y
param_dict = {param_y: 2, param_x: 1, Parameter("z"): 1}
evolution_problem = EvolutionProblem(
hamiltonian, 2, Zero, hamiltonian_value_dict=param_dict
)
with assert_raises(ValueError):
evolution_problem.validate_params()
if __name__ == "__main__":
unittest.main()

View File

@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
# (C) Copyright IBM 2021.
#
# 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

View File

@ -0,0 +1,244 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
#
# 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 TrotterQRTE. """
import unittest
from test.python.opflow import QiskitOpflowTestCase
from ddt import ddt, data, unpack
import numpy as np
from numpy.testing import assert_raises
from qiskit import BasicAer, QuantumCircuit
from qiskit.algorithms import EvolutionProblem
from qiskit.algorithms.evolvers.trotterization import (
TrotterQRTE,
)
from qiskit.circuit.library import ZGate
from qiskit.quantum_info import Statevector
from qiskit.utils import algorithm_globals, QuantumInstance
from qiskit.circuit import Parameter
from qiskit.opflow import (
X,
Z,
Zero,
VectorStateFn,
StateFn,
I,
Y,
SummedOp,
ExpectationFactory,
)
from qiskit.synthesis import SuzukiTrotter, QDrift
@ddt
class TestTrotterQRTE(QiskitOpflowTestCase):
"""TrotterQRTE tests."""
def setUp(self):
super().setUp()
self.seed = 50
algorithm_globals.random_seed = self.seed
backend_statevector = BasicAer.get_backend("statevector_simulator")
backend_qasm = BasicAer.get_backend("qasm_simulator")
self.quantum_instance = QuantumInstance(
backend=backend_statevector,
shots=1,
seed_simulator=self.seed,
seed_transpiler=self.seed,
)
self.quantum_instance_qasm = QuantumInstance(
backend=backend_qasm,
shots=8000,
seed_simulator=self.seed,
seed_transpiler=self.seed,
)
self.backends_dict = {
"qi_sv": self.quantum_instance,
"qi_qasm": self.quantum_instance_qasm,
"b_sv": backend_statevector,
"None": None,
}
self.backends_names = ["qi_qasm", "b_sv", "None", "qi_sv"]
self.backends_names_not_none = ["qi_sv", "b_sv", "qi_qasm"]
@data(
(
None,
VectorStateFn(
Statevector([0.29192658 - 0.45464871j, 0.70807342 - 0.45464871j], dims=(2,))
),
),
(
SuzukiTrotter(),
VectorStateFn(Statevector([0.29192658 - 0.84147098j, 0.0 - 0.45464871j], dims=(2,))),
),
)
@unpack
def test_trotter_qrte_trotter_single_qubit(self, product_formula, expected_state):
"""Test for default TrotterQRTE on a single qubit."""
operator = SummedOp([X, Z])
initial_state = StateFn([1, 0])
time = 1
evolution_problem = EvolutionProblem(operator, time, initial_state)
trotter_qrte = TrotterQRTE(product_formula=product_formula)
evolution_result_state_circuit = trotter_qrte.evolve(evolution_problem).evolved_state
np.testing.assert_equal(evolution_result_state_circuit.eval(), expected_state)
def test_trotter_qrte_trotter_single_qubit_aux_ops(self):
"""Test for default TrotterQRTE on a single qubit with auxiliary operators."""
operator = SummedOp([X, Z])
# LieTrotter with 1 rep
aux_ops = [X, Y]
initial_state = Zero
time = 3
evolution_problem = EvolutionProblem(operator, time, initial_state, aux_ops)
expected_evolved_state = VectorStateFn(
Statevector([0.98008514 + 0.13970775j, 0.01991486 + 0.13970775j], dims=(2,))
)
expected_aux_ops_evaluated = [(0.078073, 0.0), (0.268286, 0.0)]
expected_aux_ops_evaluated_qasm = [
(0.05799999999999995, 0.011161518713866855),
(0.2495, 0.010826759383582883),
]
for backend_name in self.backends_names_not_none:
with self.subTest(msg=f"Test {backend_name} backend."):
algorithm_globals.random_seed = 0
backend = self.backends_dict[backend_name]
expectation = ExpectationFactory.build(
operator=operator,
backend=backend,
)
trotter_qrte = TrotterQRTE(quantum_instance=backend, expectation=expectation)
evolution_result = trotter_qrte.evolve(evolution_problem)
np.testing.assert_equal(
evolution_result.evolved_state.eval(), expected_evolved_state
)
if backend_name == "qi_qasm":
expected_aux_ops_evaluated = expected_aux_ops_evaluated_qasm
np.testing.assert_array_almost_equal(
evolution_result.aux_ops_evaluated, expected_aux_ops_evaluated
)
@data(
(
SummedOp([(X ^ Y), (Y ^ X)]),
VectorStateFn(
Statevector(
[-0.41614684 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.90929743 + 0.0j], dims=(2, 2)
)
),
),
(
(Z ^ Z) + (Z ^ I) + (I ^ Z),
VectorStateFn(
Statevector(
[-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2)
)
),
),
(
Y ^ Y,
VectorStateFn(
Statevector(
[0.54030231 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.84147098j], dims=(2, 2)
)
),
),
)
@unpack
def test_trotter_qrte_trotter_two_qubits(self, operator, expected_state):
"""Test for TrotterQRTE on two qubits with various types of a Hamiltonian."""
# LieTrotter with 1 rep
initial_state = StateFn([1, 0, 0, 0])
evolution_problem = EvolutionProblem(operator, 1, initial_state)
trotter_qrte = TrotterQRTE()
evolution_result = trotter_qrte.evolve(evolution_problem)
np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state)
def test_trotter_qrte_trotter_two_qubits_with_params(self):
"""Test for TrotterQRTE on two qubits with a parametrized Hamiltonian."""
# LieTrotter with 1 rep
initial_state = StateFn([1, 0, 0, 0])
w_param = Parameter("w")
u_param = Parameter("u")
params_dict = {w_param: 2.0, u_param: 3.0}
operator = w_param * (Z ^ Z) / 2.0 + (Z ^ I) + u_param * (I ^ Z) / 3.0
time = 1
evolution_problem = EvolutionProblem(
operator, time, initial_state, hamiltonian_value_dict=params_dict
)
expected_state = VectorStateFn(
Statevector([-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2))
)
trotter_qrte = TrotterQRTE()
evolution_result = trotter_qrte.evolve(evolution_problem)
np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state)
@data(
(
Zero,
VectorStateFn(
Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,))
),
),
(
QuantumCircuit(1).compose(ZGate(), [0]),
VectorStateFn(
Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,))
),
),
)
@unpack
def test_trotter_qrte_qdrift(self, initial_state, expected_state):
"""Test for TrotterQRTE with QDrift."""
operator = SummedOp([X, Z])
time = 1
evolution_problem = EvolutionProblem(operator, time, initial_state)
algorithm_globals.random_seed = 0
trotter_qrte = TrotterQRTE(product_formula=QDrift())
evolution_result = trotter_qrte.evolve(evolution_problem)
np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state)
@data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None))
@unpack
def test_trotter_qrte_trotter_errors(self, t_param, hamiltonian_value_dict):
"""Test TrotterQRTE with raising errors."""
operator = X * Parameter("t") + Z
initial_state = Zero
time = 1
algorithm_globals.random_seed = 0
trotter_qrte = TrotterQRTE()
with assert_raises(ValueError):
evolution_problem = EvolutionProblem(
operator,
time,
initial_state,
t_param=t_param,
hamiltonian_value_dict=hamiltonian_value_dict,
)
_ = trotter_qrte.evolve(evolution_problem)
if __name__ == "__main__":
unittest.main()