diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 0c3ed197f2c6..d9cdfa036a3d 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -106,6 +106,7 @@ RealEvolver ImaginaryEvolver + TrotterQRTE EvolutionResult EvolutionProblem @@ -205,8 +206,8 @@ 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 .exceptions import AlgorithmError from .aux_ops_evaluator import eval_observables +from .evolvers.trotterization import TrotterQRTE __all__ = [ "AlgorithmResult", @@ -266,6 +268,7 @@ "NumPyEigensolver", "RealEvolver", "ImaginaryEvolver", + "TrotterQRTE", "EvolutionResult", "EvolutionProblem", "LinearSolverResult", diff --git a/qiskit/algorithms/evolvers/evolution_problem.py b/qiskit/algorithms/evolvers/evolution_problem.py index f926dbdf3d96..b069effe1747 100644 --- a/qiskit/algorithms/evolvers/evolution_problem.py +++ b/qiskit/algorithms/evolvers/evolution_problem.py @@ -23,9 +23,8 @@ 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 @@ def __init__( 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 @@ def __init__( 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}." + ) diff --git a/qiskit/algorithms/evolvers/evolution_result.py b/qiskit/algorithms/evolvers/evolution_result.py index ead37fd98dfc..1dd91d705d28 100644 --- a/qiskit/algorithms/evolvers/evolution_result.py +++ b/qiskit/algorithms/evolvers/evolution_result.py @@ -16,7 +16,7 @@ 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, ): """ diff --git a/qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py b/qiskit/algorithms/evolvers/imaginary_evolver.py similarity index 92% rename from qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py rename to qiskit/algorithms/evolvers/imaginary_evolver.py index 7743597c1e31..309bb73b08af 100644 --- a/qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py +++ b/qiskit/algorithms/evolvers/imaginary_evolver.py @@ -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): diff --git a/qiskit/algorithms/evolvers/real/real_evolver.py b/qiskit/algorithms/evolvers/real_evolver.py similarity index 92% rename from qiskit/algorithms/evolvers/real/real_evolver.py rename to qiskit/algorithms/evolvers/real_evolver.py index b6c2f2b989dc..6107facfe542 100644 --- a/qiskit/algorithms/evolvers/real/real_evolver.py +++ b/qiskit/algorithms/evolvers/real_evolver.py @@ -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): diff --git a/qiskit/algorithms/evolvers/real/__init__.py b/qiskit/algorithms/evolvers/trotterization/__init__.py similarity index 54% rename from qiskit/algorithms/evolvers/real/__init__.py rename to qiskit/algorithms/evolvers/trotterization/__init__.py index b3ac36d2a6d9..fe1b8d8aedf2 100644 --- a/qiskit/algorithms/evolvers/real/__init__.py +++ b/qiskit/algorithms/evolvers/trotterization/__init__.py @@ -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"] diff --git a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py new file mode 100644 index 000000000000..abe02e95c156 --- /dev/null +++ b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py @@ -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) diff --git a/releasenotes/notes/feature-trotter-qrte-f7b28c4fd4b361d2.yaml b/releasenotes/notes/feature-trotter-qrte-f7b28c4fd4b361d2.yaml new file mode 100644 index 000000000000..5746ab17bcff --- /dev/null +++ b/releasenotes/notes/feature-trotter-qrte-f7b28c4fd4b361d2.yaml @@ -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 diff --git a/test/python/algorithms/evolvers/test_evolution_problem.py b/test/python/algorithms/evolvers/test_evolution_problem.py index cdd472247045..0d1d18951039 100644 --- a/test/python/algorithms/evolvers/test_evolution_problem.py +++ b/test/python/algorithms/evolvers/test_evolution_problem.py @@ -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 @@ def test_init_all(self): 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 @@ def test_init_all(self): 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() diff --git a/qiskit/algorithms/evolvers/imaginary/__init__.py b/test/python/algorithms/evolvers/trotterization/__init__.py similarity index 93% rename from qiskit/algorithms/evolvers/imaginary/__init__.py rename to test/python/algorithms/evolvers/trotterization/__init__.py index b3ac36d2a6d9..96c0cf22bec9 100644 --- a/qiskit/algorithms/evolvers/imaginary/__init__.py +++ b/test/python/algorithms/evolvers/trotterization/__init__.py @@ -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 diff --git a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py new file mode 100644 index 000000000000..7baad84fe59b --- /dev/null +++ b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py @@ -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()