diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 63e44798a710..30d71a59e59c 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -94,6 +94,17 @@ VQD +Variational Quantum Time Evolution +---------------------------------- + +Classes used by variational quantum time evolution algorithms - VarQITE and VarQRTE. + +.. autosummary:: + :toctree: ../stubs/ + + evolvers.variational + + Evolvers -------- @@ -108,11 +119,14 @@ RealEvolver ImaginaryEvolver TrotterQRTE + VarQITE + VarQRTE PVQD PVQDResult EvolutionResult EvolutionProblem + Factorizers ----------- @@ -248,6 +262,8 @@ from .exceptions import AlgorithmError from .aux_ops_evaluator import eval_observables from .evolvers.trotterization import TrotterQRTE +from .evolvers.variational.var_qite import VarQITE +from .evolvers.variational.var_qrte import VarQRTE from .evolvers.pvqd import PVQD, PVQDResult __all__ = [ @@ -273,6 +289,8 @@ "RealEvolver", "ImaginaryEvolver", "TrotterQRTE", + "VarQITE", + "VarQRTE", "EvolutionResult", "EvolutionProblem", "LinearSolverResult", diff --git a/qiskit/algorithms/evolvers/evolution_problem.py b/qiskit/algorithms/evolvers/evolution_problem.py index e0f9fe3063c6..175beecd6bf6 100644 --- a/qiskit/algorithms/evolvers/evolution_problem.py +++ b/qiskit/algorithms/evolvers/evolution_problem.py @@ -35,7 +35,7 @@ def __init__( aux_operators: Optional[ListOrDict[OperatorBase]] = None, truncation_threshold: float = 1e-12, t_param: Optional[Parameter] = None, - hamiltonian_value_dict: Optional[Dict[Parameter, complex]] = None, + param_value_dict: Optional[Dict[Parameter, complex]] = None, ): """ Args: @@ -50,15 +50,15 @@ def __init__( 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. + param_value_dict: Maps free parameters in the problem to values. Depending on the + algorithm, it might refer to e.g. a Hamiltonian or an initial state. Raises: ValueError: If non-positive time of evolution is provided. """ self.t_param = t_param - self.hamiltonian_value_dict = hamiltonian_value_dict + self.param_value_dict = param_value_dict self.hamiltonian = hamiltonian self.time = time self.initial_state = initial_state @@ -95,9 +95,9 @@ def validate_params(self) -> None: 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: + if self.param_value_dict is not None: hamiltonian_dict_param_set = hamiltonian_dict_param_set.union( - set(self.hamiltonian_value_dict.keys()) + set(self.param_value_dict.keys()) ) params_set = t_param_set.union(hamiltonian_dict_param_set) hamiltonian_param_set = set(self.hamiltonian.parameters) diff --git a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py index abe02e95c156..05b8266605b7 100644 --- a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py +++ b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py @@ -187,7 +187,7 @@ def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: f"PauliSumOp | SummedOp, {type(hamiltonian)} provided." ) if isinstance(hamiltonian, OperatorBase): - hamiltonian = hamiltonian.bind_parameters(evolution_problem.hamiltonian_value_dict) + hamiltonian = hamiltonian.bind_parameters(evolution_problem.param_value_dict) if isinstance(hamiltonian, SummedOp): hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian) # the evolution gate diff --git a/qiskit/algorithms/evolvers/variational/__init__.py b/qiskit/algorithms/evolvers/variational/__init__.py new file mode 100644 index 000000000000..8936d9030853 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/__init__.py @@ -0,0 +1,139 @@ +# 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. +""" +Variational Quantum Time Evolutions (:mod:`qiskit.algorithms.evolvers.variational`) +=================================================================================== + +Algorithms for performing Variational Quantum Time Evolution of quantum states, +which can be tailored to near-term devices. +:class:`~qiskit.algorithms.evolvers.variational.VarQTE` base class exposes an interface, compliant +with the Quantum Time Evolution Framework in Qiskit Terra, that is implemented by +:class:`~qiskit.algorithms.VarQRTE` and :class:`~qiskit.algorithms.VarQITE` classes for real and +imaginary time evolution respectively. The variational approach is taken according to a variational +principle chosen by a user. + +Examples: + +.. code-block:: + + from qiskit import BasicAer + from qiskit.circuit.library import EfficientSU2 + from qiskit.opflow import SummedOp, I, Z, Y, X + from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, + ) + from qiskit.algorithms import EvolutionProblem + from qiskit.algorithms import VarQITE + + # define a Hamiltonian + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ).reduce() + + # define a parametrized initial state to be evolved + + ansatz = EfficientSU2(observable.num_qubits, reps=1) + parameters = ansatz.parameters + + # define values of initial parameters + init_param_values = np.zeros(len(ansatz.parameters)) + for i in range(len(ansatz.parameters)): + init_param_values[i] = np.pi / 2 + param_dict = dict(zip(parameters, init_param_values)) + + # define a variational principle + var_principle = ImaginaryMcLachlanPrinciple() + + # optionally define a backend + backend = BasicAer.get_backend("statevector_simulator") + + # define evolution time + time = 1 + + # define evolution problem + evolution_problem = EvolutionProblem(observable, time) + + # instantiate the algorithm + var_qite = VarQITE(ansatz, var_principle, param_dict, quantum_instance=backend) + + # run the algorithm/evolve the state + evolution_result = var_qite.evolve(evolution_problem) + +.. currentmodule:: qiskit.algorithms.evolvers.variational + +Variational Principles +---------------------- + +Variational principles can be used to simulate quantum time evolution by propagating the parameters +of a parameterized quantum circuit. + +They can be divided into two categories: + + 1) Variational Quantum Imaginary Time Evolution + Given a Hamiltonian, a time and a variational ansatz, the variational principle describes a + variational principle according to the normalized Wick-rotated Schroedinger equation. + + 2) Variational Quantum Real Time Evolution + Given a Hamiltonian, a time and a variational ansatz, the variational principle describes a + variational principle according to the Schroedinger equation. + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/class_no_inherited_members.rst + + VariationalPrinciple + RealVariationalPrinciple + ImaginaryVariationalPrinciple + RealMcLachlanPrinciple + ImaginaryMcLachlanPrinciple + +ODE solvers +----------- +ODE solvers that implement the SciPy ODE Solver interface. The Forward Euler Solver is +a preferred choice in the presence of noise. One might also use solvers provided by SciPy directly, +e.g. RK45. + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/class_no_inherited_members.rst + + ForwardEulerSolver + +""" +from .solvers.ode.forward_euler_solver import ForwardEulerSolver +from .var_qte import VarQTE +from .variational_principles.variational_principle import VariationalPrinciple +from .variational_principles import RealVariationalPrinciple, ImaginaryVariationalPrinciple +from .variational_principles.imaginary_mc_lachlan_principle import ( + ImaginaryMcLachlanPrinciple, +) +from .variational_principles.real_mc_lachlan_principle import ( + RealMcLachlanPrinciple, +) + + +__all__ = [ + "ForwardEulerSolver", + "VarQTE", + "VariationalPrinciple", + "RealVariationalPrinciple", + "ImaginaryVariationalPrinciple", + "RealMcLachlanPrinciple", + "ImaginaryMcLachlanPrinciple", +] diff --git a/qiskit/algorithms/evolvers/variational/solvers/__init__.py b/qiskit/algorithms/evolvers/variational/solvers/__init__.py new file mode 100644 index 000000000000..6d273c0618c9 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/__init__.py @@ -0,0 +1,44 @@ +# 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. + +""" +Solvers (:mod:`qiskit.algorithms.evolvers.variational.solvers`) +=============================================================== + +This package contains the necessary classes to solve systems of equations arising in the +Variational Quantum Time Evolution. They include ordinary differential equations (ODE) which +describe ansatz parameter propagation and systems of linear equations. + + +Systems of Linear Equations Solver +---------------------------------- + +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/class_no_inherited_members.rst + + VarQTELinearSolver + + +ODE Solver +---------- +.. autosummary:: + :toctree: ../stubs/ + :template: autosummary/class_no_inherited_members.rst + + VarQTEOdeSolver +""" + +from qiskit.algorithms.evolvers.variational.solvers.ode.var_qte_ode_solver import VarQTEOdeSolver +from qiskit.algorithms.evolvers.variational.solvers.var_qte_linear_solver import VarQTELinearSolver + +__all__ = ["VarQTELinearSolver", "VarQTEOdeSolver"] diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/__init__.py b/qiskit/algorithms/evolvers/variational/solvers/ode/__init__.py new file mode 100644 index 000000000000..8fbaef6f85d1 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/__init__.py @@ -0,0 +1,13 @@ +# 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. + +"""ODE Solvers""" diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/abstract_ode_function.py b/qiskit/algorithms/evolvers/variational/solvers/ode/abstract_ode_function.py new file mode 100644 index 000000000000..a443a26d1888 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/abstract_ode_function.py @@ -0,0 +1,52 @@ +# 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. + +"""Abstract class for generating ODE functions.""" + +from abc import ABC, abstractmethod +from typing import Iterable, Dict, Optional +from qiskit.circuit import Parameter +from ..var_qte_linear_solver import ( + VarQTELinearSolver, +) + + +class AbstractOdeFunction(ABC): + """Abstract class for generating ODE functions.""" + + def __init__( + self, + varqte_linear_solver: VarQTELinearSolver, + error_calculator, + param_dict: Dict[Parameter, complex], + t_param: Optional[Parameter] = None, + ) -> None: + + self._varqte_linear_solver = varqte_linear_solver + self._error_calculator = error_calculator + self._param_dict = param_dict + self._t_param = t_param + + @abstractmethod + def var_qte_ode_function(self, time: float, parameters_values: Iterable) -> Iterable: + """ + Evaluates an ODE function for a given time and parameter values. It is used by an ODE + solver. + + Args: + time: Current time of evolution. + parameters_values: Current values of parameters. + + Returns: + ODE gradient arising from solving a system of linear equations. + """ + pass diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/forward_euler_solver.py b/qiskit/algorithms/evolvers/variational/solvers/ode/forward_euler_solver.py new file mode 100644 index 000000000000..284b3106fa8a --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/forward_euler_solver.py @@ -0,0 +1,73 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Forward Euler ODE solver.""" + +from typing import Sequence + +import numpy as np +from scipy.integrate import OdeSolver +from scipy.integrate._ivp.base import ConstantDenseOutput + + +class ForwardEulerSolver(OdeSolver): + """Forward Euler ODE solver.""" + + def __init__( + self, + function: callable, + t0: float, + y0: Sequence, + t_bound: float, + vectorized: bool = False, + support_complex: bool = False, + num_t_steps: int = 15, + ): + """ + Forward Euler ODE solver that implements an interface from SciPy. + + Args: + function: Right-hand side of the system. The calling signature is ``fun(t, y)``. Here + ``t`` is a scalar, and there are two options for the ndarray ``y``: + It can either have shape (n,); then ``fun`` must return array_like with + shape (n,). Alternatively it can have shape (n, k); then ``fun`` + must return an array_like with shape (n, k), i.e., each column + corresponds to a single column in ``y``. The choice between the two + options is determined by `vectorized` argument (see below). The + vectorized implementation allows a faster approximation of the Jacobian + by finite differences (required for this solver). + t0: Initial time. + y0: Initial state. + t_bound: Boundary time - the integration won't continue beyond it. It also determines + the direction of the integration. + vectorized: Whether ``fun`` is implemented in a vectorized fashion. Default is False. + support_complex: Whether integration in a complex domain should be supported. + Generally determined by a derived solver class capabilities. Default is False. + num_t_steps: Number of time steps for the forward Euler method. + """ + self.y_old = None + self.step_length = (t_bound - t0) / num_t_steps + super().__init__(function, t0, y0, t_bound, vectorized, support_complex) + + def _step_impl(self): + """ + Takes an Euler step. + """ + try: + self.y_old = self.y + self.y = list(np.add(self.y, self.step_length * self.fun(self.t, self.y))) + self.t += self.step_length + return True, None + except Exception as ex: # pylint: disable=broad-except + return False, f"Unknown ODE solver error: {str(ex)}." + + def _dense_output_impl(self): + return ConstantDenseOutput(self.t_old, self.t, self.y_old) diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function.py b/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function.py new file mode 100644 index 000000000000..0d142262868c --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function.py @@ -0,0 +1,43 @@ +# 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. + +"""Class for generating ODE functions based on ODE gradients.""" +from typing import Iterable + +from ..ode.abstract_ode_function import ( + AbstractOdeFunction, +) + + +class OdeFunction(AbstractOdeFunction): + """Class for generating ODE functions based on ODE gradients.""" + + def var_qte_ode_function(self, time: float, parameters_values: Iterable) -> Iterable: + """ + Evaluates an ODE function for a given time and parameter values. It is used by an ODE + solver. + + Args: + time: Current time of evolution. + parameters_values: Current values of parameters. + + Returns: + ODE gradient arising from solving a system of linear equations. + """ + current_param_dict = dict(zip(self._param_dict.keys(), parameters_values)) + + ode_grad_res, _, _ = self._varqte_linear_solver.solve_lse( + current_param_dict, + time, + ) + + return ode_grad_res diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function_factory.py b/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function_factory.py new file mode 100644 index 000000000000..2dce88a817b1 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/ode_function_factory.py @@ -0,0 +1,83 @@ +# 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. + +"""Abstract class for generating ODE functions.""" + +from abc import ABC +from enum import Enum +from typing import Dict, Any, Optional, Callable + +import numpy as np + +from qiskit.circuit import Parameter +from .abstract_ode_function import AbstractOdeFunction +from .ode_function import OdeFunction +from ..var_qte_linear_solver import ( + VarQTELinearSolver, +) + + +class OdeFunctionType(Enum): + """Types of ODE functions for VatQTE algorithms.""" + + # more will be supported in the near future + STANDARD_ODE = "STANDARD_ODE" + + +class OdeFunctionFactory(ABC): + """Factory for building ODE functions.""" + + def __init__( + self, + ode_function_type: OdeFunctionType = OdeFunctionType.STANDARD_ODE, + lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None, + ) -> None: + """ + Args: + ode_function_type: An Enum that defines a type of an ODE function to be built. If + not provided, a default ``STANDARD_ODE`` is used. + lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to + solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` + solver is used. + """ + self.ode_function_type = ode_function_type + self.lse_solver = lse_solver + + def _build( + self, + varqte_linear_solver: VarQTELinearSolver, + error_calculator: Any, + param_dict: Dict[Parameter, complex], + t_param: Optional[Parameter] = None, + ) -> AbstractOdeFunction: + """ + Initializes an ODE function specified in the class. + + Args: + varqte_linear_solver: Solver of LSE for the VarQTE algorithm. + error_calculator: Calculator of errors for error-based ODE functions. + param_dict: Dictionary which relates parameter values to the parameters in the ansatz. + t_param: Time parameter in case of a time-dependent Hamiltonian. + + Returns: + An ODE function. + + Raises: + ValueError: If unsupported ODE function provided. + + """ + if self.ode_function_type == OdeFunctionType.STANDARD_ODE: + return OdeFunction(varqte_linear_solver, error_calculator, param_dict, t_param) + raise ValueError( + f"Unsupported ODE function provided: {self.ode_function_type}." + f" Only {[tp.value for tp in OdeFunctionType]} are supported." + ) diff --git a/qiskit/algorithms/evolvers/variational/solvers/ode/var_qte_ode_solver.py b/qiskit/algorithms/evolvers/variational/solvers/ode/var_qte_ode_solver.py new file mode 100644 index 000000000000..525769ddc96c --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/ode/var_qte_ode_solver.py @@ -0,0 +1,83 @@ +# 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. + +"""Class for solving ODEs for Quantum Time Evolution.""" +from functools import partial +from typing import List, Union, Type, Optional + +import numpy as np +from scipy.integrate import OdeSolver, solve_ivp + +from .abstract_ode_function import ( + AbstractOdeFunction, +) +from .forward_euler_solver import ForwardEulerSolver + + +class VarQTEOdeSolver: + """Class for solving ODEs for Quantum Time Evolution.""" + + def __init__( + self, + init_params: List[complex], + ode_function: AbstractOdeFunction, + ode_solver: Union[Type[OdeSolver], str] = ForwardEulerSolver, + num_timesteps: Optional[int] = None, + ) -> None: + """ + Initialize ODE Solver. + + Args: + init_params: Set of initial parameters for time 0. + ode_function: Generates the ODE function. + ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a + string indicating a valid method offered by SciPy. + num_timesteps: The number of timesteps to take. If None, it is + automatically selected to achieve a timestep of approximately 0.01. Only + relevant in case of the ``ForwardEulerSolver``. + """ + self._init_params = init_params + self._ode_function = ode_function.var_qte_ode_function + self._ode_solver = ode_solver + self._num_timesteps = num_timesteps + + def run(self, evolution_time: float) -> List[complex]: + """ + Finds numerical solution with ODE Solver. + + Args: + evolution_time: Evolution time. + + Returns: + List of parameters found by an ODE solver for a given ODE function callable. + """ + # determine the number of timesteps and set the timestep + num_timesteps = ( + int(np.ceil(evolution_time / 0.01)) + if self._num_timesteps is None + else self._num_timesteps + ) + + if self._ode_solver == ForwardEulerSolver: + solve = partial(solve_ivp, num_t_steps=num_timesteps) + else: + solve = solve_ivp + + sol = solve( + self._ode_function, + (0, evolution_time), + self._init_params, + method=self._ode_solver, + ) + final_params_vals = [lst[-1] for lst in sol.y] + + return final_params_vals diff --git a/qiskit/algorithms/evolvers/variational/solvers/var_qte_linear_solver.py b/qiskit/algorithms/evolvers/variational/solvers/var_qte_linear_solver.py new file mode 100644 index 000000000000..1c4a61963374 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/solvers/var_qte_linear_solver.py @@ -0,0 +1,160 @@ +# 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. + +"""Class for solving linear equations for Quantum Time Evolution.""" + +from typing import Union, List, Dict, Optional, Callable + +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.algorithms.evolvers.variational.variational_principles.variational_principle import ( + VariationalPrinciple, +) +from qiskit.circuit import Parameter +from qiskit.opflow import ( + CircuitSampler, + OperatorBase, + ExpectationBase, +) +from qiskit.providers import Backend +from qiskit.utils import QuantumInstance +from qiskit.utils.backend_utils import is_aer_provider + + +class VarQTELinearSolver: + """Class for solving linear equations for Quantum Time Evolution.""" + + def __init__( + self, + var_principle: VariationalPrinciple, + hamiltonian: OperatorBase, + ansatz: QuantumCircuit, + gradient_params: List[Parameter], + t_param: Optional[Parameter] = None, + lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None, + imag_part_tol: float = 1e-7, + expectation: Optional[ExpectationBase] = None, + quantum_instance: Optional[QuantumInstance] = None, + ) -> None: + """ + Args: + var_principle: Variational Principle to be used. + hamiltonian: + Operator used for Variational Quantum Time Evolution. + The operator may be given either as a composed op consisting of a Hermitian + observable and a ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a + ``ComboFn``. + The latter case enables the evaluation of a Quantum Natural Gradient. + ansatz: Quantum state in the form of a parametrized quantum circuit. + gradient_params: List of parameters with respect to which gradients should be computed. + t_param: Time parameter in case of a time-dependent Hamiltonian. + lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to + solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` + solver is used. + imag_part_tol: Allowed value of an imaginary part that can be neglected if no + imaginary part is expected. + expectation: An instance of ``ExpectationBase`` used for calculating a metric tensor + and an evolution gradient. If ``None`` provided, a ``PauliExpectation`` is used. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + """ + self._var_principle = var_principle + self._hamiltonian = hamiltonian + self._ansatz = ansatz + self._gradient_params = gradient_params + self._bind_params = gradient_params + [t_param] if t_param else gradient_params + self._time_param = t_param + self.lse_solver = lse_solver + self._quantum_instance = None + self._circuit_sampler = None + self._imag_part_tol = imag_part_tol + self._expectation = expectation + if quantum_instance is not None: + self.quantum_instance = quantum_instance + + @property + def lse_solver(self) -> Callable[[np.ndarray, np.ndarray], np.ndarray]: + """Returns an LSE solver callable.""" + return self._lse_solver + + @lse_solver.setter + def lse_solver( + self, lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] + ) -> None: + """Sets an LSE solver. Uses a ``np.linalg.lstsq`` callable if ``None`` provided.""" + if lse_solver is None: + lse_solver = lambda a, b: np.linalg.lstsq(a, b, rcond=1e-2)[0] + + self._lse_solver = lse_solver + + @property + def quantum_instance(self) -> Optional[QuantumInstance]: + """Returns quantum instance.""" + return self._quantum_instance + + @quantum_instance.setter + def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: + """Sets quantum_instance""" + if not isinstance(quantum_instance, QuantumInstance): + quantum_instance = QuantumInstance(quantum_instance) + + self._quantum_instance = quantum_instance + self._circuit_sampler = CircuitSampler( + quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) + ) + + def solve_lse( + self, + param_dict: Dict[Parameter, complex], + time_value: Optional[float] = None, + ) -> (Union[List, np.ndarray], Union[List, np.ndarray], np.ndarray): + """ + Solve the system of linear equations underlying McLachlan's variational principle for the + calculation without error bounds. + + Args: + param_dict: Dictionary which relates parameter values to the parameters in the ansatz. + time_value: Time value that will be bound to ``t_param``. It is required if ``t_param`` + is not ``None``. + + Returns: + Solution to the LSE, A from Ax=b, b from Ax=b. + """ + param_values = list(param_dict.values()) + if self._time_param is not None: + param_values.append(time_value) + + metric_tensor_lse_lhs = self._var_principle.metric_tensor( + self._ansatz, + self._bind_params, + self._gradient_params, + param_values, + self._expectation, + self._quantum_instance, + ) + evolution_grad_lse_rhs = self._var_principle.evolution_grad( + self._hamiltonian, + self._ansatz, + self._circuit_sampler, + param_dict, + self._bind_params, + self._gradient_params, + param_values, + self._expectation, + self._quantum_instance, + ) + + x = self._lse_solver(metric_tensor_lse_lhs, evolution_grad_lse_rhs) + + return np.real(x), metric_tensor_lse_lhs, evolution_grad_lse_rhs diff --git a/qiskit/algorithms/evolvers/variational/var_qite.py b/qiskit/algorithms/evolvers/variational/var_qite.py new file mode 100644 index 000000000000..5d53cc1eef63 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/var_qite.py @@ -0,0 +1,125 @@ +# 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. + +"""Variational Quantum Imaginary Time Evolution algorithm.""" +from typing import Optional, Union, Type, Callable, List, Dict + +import numpy as np +from scipy.integrate import OdeSolver + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.opflow import ExpectationBase, OperatorBase +from qiskit.algorithms.evolvers.imaginary_evolver import ImaginaryEvolver +from qiskit.utils import QuantumInstance +from . import ImaginaryMcLachlanPrinciple +from .solvers.ode.forward_euler_solver import ForwardEulerSolver +from .variational_principles import ImaginaryVariationalPrinciple +from .var_qte import VarQTE + + +class VarQITE(VarQTE, ImaginaryEvolver): + """Variational Quantum Imaginary Time Evolution algorithm. + + .. code-block::python + + from qiskit.algorithms import EvolutionProblem + from qiskit.algorithms import VarQITE + from qiskit import BasicAer + from qiskit.circuit.library import EfficientSU2 + from qiskit.opflow import SummedOp, I, Z, Y, X + from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, + ) + from qiskit.algorithms import EvolutionProblem + import numpy as np + + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ).reduce() + + ansatz = EfficientSU2(observable.num_qubits, reps=1) + parameters = ansatz.parameters + init_param_values = np.zeros(len(ansatz.parameters)) + for i in range(len(ansatz.ordered_parameters)): + init_param_values[i] = np.pi / 2 + param_dict = dict(zip(parameters, init_param_values)) + var_principle = ImaginaryMcLachlanPrinciple() + backend = BasicAer.get_backend("statevector_simulator") + time = 1 + evolution_problem = EvolutionProblem(observable, time) + var_qite = VarQITE(ansatz, var_principle, param_dict, quantum_instance=backend) + evolution_result = var_qite.evolve(evolution_problem) + """ + + def __init__( + self, + ansatz: Union[OperatorBase, QuantumCircuit], + variational_principle: Optional[ImaginaryVariationalPrinciple] = None, + initial_parameters: Optional[ + Union[Dict[Parameter, complex], List[complex], np.ndarray] + ] = None, + ode_solver: Union[Type[OdeSolver], str] = ForwardEulerSolver, + lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None, + num_timesteps: Optional[int] = None, + expectation: Optional[ExpectationBase] = None, + imag_part_tol: float = 1e-7, + num_instability_tol: float = 1e-7, + quantum_instance: Optional[QuantumInstance] = None, + ) -> None: + r""" + Args: + ansatz: Ansatz to be used for variational time evolution. + variational_principle: Variational Principle to be used. Defaults to + ``ImaginaryMcLachlanPrinciple``. + initial_parameters: Initial parameter values for an ansatz. If ``None`` provided, + they are initialized uniformly at random. + ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a + string indicating a valid method offered by SciPy. + lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to + solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` + solver is used. + num_timesteps: The number of timesteps to take. If None, it is + automatically selected to achieve a timestep of approximately 0.01. Only + relevant in case of the ``ForwardEulerSolver``. + expectation: An instance of ``ExpectationBase`` which defines a method for calculating + a metric tensor and an evolution gradient and, if provided, expectation values of + ``EvolutionProblem.aux_operators``. + imag_part_tol: Allowed value of an imaginary part that can be neglected if no + imaginary part is expected. + num_instability_tol: The amount of negative value that is allowed to be + rounded up to 0 for quantities that are expected to be non-negative. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on NumPy matrix multiplication + (which might be slow for larger numbers of qubits). + """ + if variational_principle is None: + variational_principle = ImaginaryMcLachlanPrinciple() + super().__init__( + ansatz, + variational_principle, + initial_parameters, + ode_solver, + lse_solver=lse_solver, + num_timesteps=num_timesteps, + expectation=expectation, + imag_part_tol=imag_part_tol, + num_instability_tol=num_instability_tol, + quantum_instance=quantum_instance, + ) diff --git a/qiskit/algorithms/evolvers/variational/var_qrte.py b/qiskit/algorithms/evolvers/variational/var_qrte.py new file mode 100644 index 000000000000..c0846a7159b7 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/var_qrte.py @@ -0,0 +1,126 @@ +# 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. + +"""Variational Quantum Real Time Evolution algorithm.""" +from typing import Optional, Union, Type, Callable, List, Dict + +import numpy as np +from scipy.integrate import OdeSolver + +from qiskit import QuantumCircuit +from qiskit.algorithms.evolvers.real_evolver import RealEvolver +from qiskit.circuit import Parameter +from qiskit.opflow import ExpectationBase, OperatorBase +from qiskit.utils import QuantumInstance +from . import RealMcLachlanPrinciple +from .solvers.ode.forward_euler_solver import ForwardEulerSolver +from .variational_principles import RealVariationalPrinciple +from .var_qte import VarQTE + + +class VarQRTE(VarQTE, RealEvolver): + """Variational Quantum Real Time Evolution algorithm. + + .. code-block::python + + from qiskit.algorithms import EvolutionProblem + from qiskit.algorithms import VarQITE + from qiskit import BasicAer + from qiskit.circuit.library import EfficientSU2 + from qiskit.opflow import SummedOp, I, Z, Y, X + from qiskit.algorithms.evolvers.variational import ( + RealMcLachlanPrinciple, + ) + from qiskit.algorithms import EvolutionProblem + import numpy as np + + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ).reduce() + + ansatz = EfficientSU2(observable.num_qubits, reps=1) + parameters = ansatz.parameters + init_param_values = np.zeros(len(ansatz.parameters)) + for i in range(len(ansatz.parameters)): + init_param_values[i] = np.pi / 2 + param_dict = dict(zip(parameters, init_param_values)) + var_principle = RealMcLachlanPrinciple() + backend = BasicAer.get_backend("statevector_simulator") + time = 1 + evolution_problem = EvolutionProblem(observable, time) + var_qrte = VarQRTE(ansatz, var_principle, param_dict, quantum_instance=backend) + evolution_result = var_qite.evolve(evolution_problem) + """ + + def __init__( + self, + ansatz: Union[OperatorBase, QuantumCircuit], + variational_principle: Optional[RealVariationalPrinciple] = None, + initial_parameters: Optional[ + Union[Dict[Parameter, complex], List[complex], np.ndarray] + ] = None, + ode_solver: Union[Type[OdeSolver], str] = ForwardEulerSolver, + lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None, + num_timesteps: Optional[int] = None, + expectation: Optional[ExpectationBase] = None, + imag_part_tol: float = 1e-7, + num_instability_tol: float = 1e-7, + quantum_instance: Optional[QuantumInstance] = None, + ) -> None: + r""" + Args: + ansatz: Ansatz to be used for variational time evolution. + variational_principle: Variational Principle to be used. Defaults to + ``RealMcLachlanPrinciple``. + initial_parameters: Initial parameter values for an ansatz. If ``None`` provided, + they are initialized uniformly at random. + ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a + string indicating a valid method offered by SciPy. + lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to + solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` + solver is used. + num_timesteps: The number of timesteps to take. If None, it is + automatically selected to achieve a timestep of approximately 0.01. Only + relevant in case of the ``ForwardEulerSolver``. + expectation: An instance of ``ExpectationBase`` which defines a method for calculating + a metric tensor and an evolution gradient and, if provided, expectation values of + ``EvolutionProblem.aux_operators``. + imag_part_tol: Allowed value of an imaginary part that can be neglected if no + imaginary part is expected. + num_instability_tol: The amount of negative value that is allowed to be + rounded up to 0 for quantities that are expected to be + non-negative. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + """ + if variational_principle is None: + variational_principle = RealMcLachlanPrinciple() + super().__init__( + ansatz, + variational_principle, + initial_parameters, + ode_solver, + lse_solver=lse_solver, + num_timesteps=num_timesteps, + expectation=expectation, + imag_part_tol=imag_part_tol, + num_instability_tol=num_instability_tol, + quantum_instance=quantum_instance, + ) diff --git a/qiskit/algorithms/evolvers/variational/var_qte.py b/qiskit/algorithms/evolvers/variational/var_qte.py new file mode 100644 index 000000000000..7edc898037e0 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/var_qte.py @@ -0,0 +1,303 @@ +# 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. + +"""The Variational Quantum Time Evolution Interface""" +from abc import ABC +from typing import Optional, Union, Dict, List, Any, Type, Callable + +import numpy as np +from scipy.integrate import OdeSolver + +from qiskit import QuantumCircuit +from qiskit.algorithms.aux_ops_evaluator import eval_observables +from qiskit.algorithms.evolvers.evolution_problem import EvolutionProblem +from qiskit.algorithms.evolvers.evolution_result import EvolutionResult +from qiskit.circuit import Parameter +from qiskit.providers import Backend +from qiskit.utils import QuantumInstance +from qiskit.opflow import ( + CircuitSampler, + OperatorBase, + ExpectationBase, +) +from qiskit.utils.backend_utils import is_aer_provider +from .solvers.ode.forward_euler_solver import ForwardEulerSolver +from .solvers.ode.ode_function_factory import OdeFunctionFactory +from .solvers.var_qte_linear_solver import ( + VarQTELinearSolver, +) +from .variational_principles.variational_principle import ( + VariationalPrinciple, +) +from .solvers.ode.var_qte_ode_solver import ( + VarQTEOdeSolver, +) + + +class VarQTE(ABC): + """Variational Quantum Time Evolution. + + Algorithms that use variational principles to compute a time evolution for a given + Hermitian operator (Hamiltonian) and a quantum state prepared by a parameterized quantum circuit. + + References: + + [1] Benjamin, Simon C. et al. (2019). + Theory of variational quantum simulation. ``_ + """ + + def __init__( + self, + ansatz: Union[OperatorBase, QuantumCircuit], + variational_principle: VariationalPrinciple, + initial_parameters: Optional[ + Union[Dict[Parameter, complex], List[complex], np.ndarray] + ] = None, + ode_solver: Union[Type[OdeSolver], str] = ForwardEulerSolver, + lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None, + num_timesteps: Optional[int] = None, + expectation: Optional[ExpectationBase] = None, + imag_part_tol: float = 1e-7, + num_instability_tol: float = 1e-7, + quantum_instance: Optional[QuantumInstance] = None, + ) -> None: + r""" + Args: + ansatz: Ansatz to be used for variational time evolution. + variational_principle: Variational Principle to be used. + initial_parameters: Initial parameter values for an ansatz. If ``None`` provided, + they are initialized uniformly at random. + ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a + string indicating a valid method offered by SciPy. + lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to + solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` + solver is used. + num_timesteps: The number of timesteps to take. If None, it is + automatically selected to achieve a timestep of approximately 0.01. Only + relevant in case of the ``ForwardEulerSolver``. + expectation: An instance of ``ExpectationBase`` which defines a method for calculating + a metric tensor and an evolution gradient and, if provided, expectation values of + ``EvolutionProblem.aux_operators``. + imag_part_tol: Allowed value of an imaginary part that can be neglected if no + imaginary part is expected. + num_instability_tol: The amount of negative value that is allowed to be + rounded up to 0 for quantities that are expected to be + non-negative. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + """ + super().__init__() + self.ansatz = ansatz + self.variational_principle = variational_principle + self.initial_parameters = initial_parameters + self._quantum_instance = None + if quantum_instance is not None: + self.quantum_instance = quantum_instance + self.expectation = expectation + self.num_timesteps = num_timesteps + self.lse_solver = lse_solver + # OdeFunction abstraction kept for potential extensions - unclear at the moment; + # currently hidden from the user + self._ode_function_factory = OdeFunctionFactory(lse_solver=lse_solver) + self.ode_solver = ode_solver + self.imag_part_tol = imag_part_tol + self.num_instability_tol = num_instability_tol + + @property + def quantum_instance(self) -> Optional[QuantumInstance]: + """Returns quantum instance.""" + return self._quantum_instance + + @quantum_instance.setter + def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: + """Sets quantum_instance""" + if not isinstance(quantum_instance, QuantumInstance): + quantum_instance = QuantumInstance(quantum_instance) + + self._quantum_instance = quantum_instance + self._circuit_sampler = CircuitSampler( + quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) + ) + + def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: + """ + Apply Variational Quantum Imaginary Time Evolution (VarQITE) w.r.t. the given + operator. + + Args: + evolution_problem: Instance defining an evolution problem. + Returns: + Result of the evolution which includes a quantum circuit with bound parameters as an + evolved state and, if provided, observables evaluated on the evolved state using + a ``quantum_instance`` and ``expectation`` provided. + + Raises: + ValueError: If no ``initial_state`` is included in the ``evolution_problem``. + """ + self._validate_aux_ops(evolution_problem) + + if evolution_problem.initial_state is not None: + raise ValueError("initial_state provided but not applicable to VarQTE.") + + init_state_param_dict = self._create_init_state_param_dict( + self.initial_parameters, self.ansatz.parameters + ) + + error_calculator = None # TODO will be supported in another PR + + evolved_state = self._evolve( + init_state_param_dict, + evolution_problem.hamiltonian, + evolution_problem.time, + evolution_problem.t_param, + error_calculator, + ) + + evaluated_aux_ops = None + if evolution_problem.aux_operators is not None: + evaluated_aux_ops = eval_observables( + self.quantum_instance, + evolved_state, + evolution_problem.aux_operators, + self.expectation, + ) + + return EvolutionResult(evolved_state, evaluated_aux_ops) + + def _evolve( + self, + init_state_param_dict: Dict[Parameter, complex], + hamiltonian: OperatorBase, + time: float, + t_param: Optional[Parameter] = None, + error_calculator: Any = None, + ) -> OperatorBase: + r""" + Helper method for performing time evolution. Works both for imaginary and real case. + + Args: + init_state_param_dict: Parameter dictionary with initial values for a given + parametrized state/ansatz. If no initial parameter values are provided, they are + initialized uniformly at random. + hamiltonian: + Operator used for Variational Quantum Imaginary Time Evolution (VarQTE). + The operator may be given either as a composed op consisting of a Hermitian + observable and a ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a + ``ComboFn``. + The latter case enables the evaluation of a Quantum Natural Gradient. + time: Total time of evolution. + t_param: Time parameter in case of a time-dependent Hamiltonian. + error_calculator: Not yet supported. Calculator of errors for error-based ODE functions. + + Returns: + Result of the evolution which is a quantum circuit with bound parameters as an + evolved state. + """ + + init_state_parameters = list(init_state_param_dict.keys()) + init_state_parameters_values = list(init_state_param_dict.values()) + + linear_solver = VarQTELinearSolver( + self.variational_principle, + hamiltonian, + self.ansatz, + init_state_parameters, + t_param, + self._ode_function_factory.lse_solver, + self.imag_part_tol, + self.expectation, + self._quantum_instance, + ) + + # Convert the operator that holds the Hamiltonian and ansatz into a NaturalGradient operator + ode_function = self._ode_function_factory._build( + linear_solver, error_calculator, init_state_param_dict, t_param + ) + + ode_solver = VarQTEOdeSolver( + init_state_parameters_values, ode_function, self.ode_solver, self.num_timesteps + ) + parameter_values = ode_solver.run(time) + param_dict_from_ode = dict(zip(init_state_parameters, parameter_values)) + + return self.ansatz.assign_parameters(param_dict_from_ode) + + @staticmethod + def _create_init_state_param_dict( + param_values: Union[Dict[Parameter, complex], List[complex], np.ndarray], + init_state_parameters: List[Parameter], + ) -> Dict[Parameter, complex]: + r""" + If ``param_values`` is a dictionary, it looks for parameters present in an initial state + (an ansatz) in a ``param_values``. Based on that, it creates a new dictionary containing + only parameters present in an initial state and their respective values. + If ``param_values`` is a list of values, it creates a new dictionary containing + parameters present in an initial state and their respective values. + If no ``param_values`` is provided, parameter values are chosen uniformly at random. + + Args: + param_values: Dictionary which relates parameter values to the parameters or a list of + values. + init_state_parameters: Parameters present in a quantum state. + + Returns: + Dictionary that maps parameters of an initial state to some values. + + Raises: + ValueError: If the dictionary with parameter values provided does not include all + parameters present in the initial state or if the list of values provided is not the + same length as the list of parameters. + TypeError: If an unsupported type of ``param_values`` provided. + """ + if param_values is None: + init_state_parameter_values = np.random.random(len(init_state_parameters)) + elif isinstance(param_values, dict): + init_state_parameter_values = [] + for param in init_state_parameters: + if param in param_values.keys(): + init_state_parameter_values.append(param_values[param]) + else: + raise ValueError( + f"The dictionary with parameter values provided does not " + f"include all parameters present in the initial state." + f"Parameters present in the state: {init_state_parameters}, " + f"parameters in the dictionary: " + f"{list(param_values.keys())}." + ) + elif isinstance(param_values, (list, np.ndarray)): + if len(init_state_parameters) != len(param_values): + raise ValueError( + f"Initial state has {len(init_state_parameters)} parameters and the" + f" list of values has {len(param_values)} elements. They should be" + f"equal in length." + ) + init_state_parameter_values = param_values + else: + raise TypeError(f"Unsupported type of param_values provided: {type(param_values)}.") + + init_state_param_dict = dict(zip(init_state_parameters, init_state_parameter_values)) + return init_state_param_dict + + def _validate_aux_ops(self, evolution_problem: EvolutionProblem) -> None: + if evolution_problem.aux_operators is not None: + if self.quantum_instance is None: + raise ValueError( + "aux_operators where provided for evaluations but no ``quantum_instance`` " + "was provided." + ) + + if self.expectation is None: + raise ValueError( + "aux_operators where provided for evaluations but no ``expectation`` " + "was provided." + ) diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/__init__.py b/qiskit/algorithms/evolvers/variational/variational_principles/__init__.py new file mode 100644 index 000000000000..7c508f3921de --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/__init__.py @@ -0,0 +1,25 @@ +# 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. + +"""Variational Principles""" + +from .imaginary_mc_lachlan_principle import ImaginaryMcLachlanPrinciple +from .imaginary_variational_principle import ImaginaryVariationalPrinciple +from .real_mc_lachlan_principle import RealMcLachlanPrinciple +from .real_variational_principle import RealVariationalPrinciple + +__all__ = [ + "ImaginaryMcLachlanPrinciple", + "ImaginaryVariationalPrinciple", + "RealMcLachlanPrinciple", + "RealVariationalPrinciple", +] diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py b/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py new file mode 100644 index 000000000000..7a0c46b794f2 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py @@ -0,0 +1,76 @@ +# 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. + +"""Class for an Imaginary McLachlan's Variational Principle.""" +from typing import Dict, List, Optional + +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.opflow import StateFn, OperatorBase, CircuitSampler, ExpectationBase +from qiskit.utils import QuantumInstance +from .imaginary_variational_principle import ( + ImaginaryVariationalPrinciple, +) + + +class ImaginaryMcLachlanPrinciple(ImaginaryVariationalPrinciple): + """Class for an Imaginary McLachlan's Variational Principle. It aims to minimize the distance + between both sides of the Wick-rotated Schrödinger equation with a quantum state given as a + parametrized trial state. The principle leads to a system of linear equations handled by a + linear solver. The imaginary variant means that we consider imaginary time dynamics. + """ + + def evolution_grad( + self, + hamiltonian: OperatorBase, + ansatz: QuantumCircuit, + circuit_sampler: CircuitSampler, + param_dict: Dict[Parameter, complex], + bind_params: List[Parameter], + gradient_params: List[Parameter], + param_values: List[complex], + expectation: Optional[ExpectationBase] = None, + quantum_instance: Optional[QuantumInstance] = None, + ) -> np.ndarray: + """ + Calculates an evolution gradient according to the rules of this variational principle. + + Args: + hamiltonian: Operator used for Variational Quantum Time Evolution. The operator may be + given either as a composed op consisting of a Hermitian observable and a + ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a ``ComboFn``. The + latter case enables the evaluation of a Quantum Natural Gradient. + ansatz: Quantum state in the form of a parametrized quantum circuit. + circuit_sampler: A circuit sampler. + param_dict: Dictionary which relates parameter values to the parameters in the ansatz. + bind_params: List of parameters that are supposed to be bound. + gradient_params: List of parameters with respect to which gradients should be computed. + param_values: Values of parameters to be bound. + expectation: An instance of ``ExpectationBase`` used for calculating an evolution + gradient. If ``None`` provided, a ``PauliExpectation`` is used. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + + Returns: + An evolution gradient. + """ + if self._evolution_gradient_callable is None: + operator = StateFn(hamiltonian, is_measurement=True) @ StateFn(ansatz) + self._evolution_gradient_callable = self._evolution_gradient.gradient_wrapper( + operator, bind_params, gradient_params, quantum_instance, expectation + ) + evolution_grad_lse_rhs = -0.5 * self._evolution_gradient_callable(param_values) + + return evolution_grad_lse_rhs diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_variational_principle.py b/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_variational_principle.py new file mode 100644 index 000000000000..bcd60241942a --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/imaginary_variational_principle.py @@ -0,0 +1,24 @@ +# 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. + +"""Abstract class for an Imaginary Variational Principle.""" + +from abc import ABC + +from ..variational_principles.variational_principle import ( + VariationalPrinciple, +) + + +class ImaginaryVariationalPrinciple(VariationalPrinciple, ABC): + """Abstract class for an Imaginary Variational Principle. The imaginary variant means that we + consider imaginary time dynamics.""" diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/real_mc_lachlan_principle.py b/qiskit/algorithms/evolvers/variational/variational_principles/real_mc_lachlan_principle.py new file mode 100644 index 000000000000..ddc6a17ed879 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/real_mc_lachlan_principle.py @@ -0,0 +1,150 @@ +# 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. + +"""Class for a Real McLachlan's Variational Principle.""" +from typing import Union, Dict, List, Optional + +import numpy as np +from numpy import real + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.opflow import ( + StateFn, + SummedOp, + Y, + I, + PauliExpectation, + CircuitQFI, + CircuitSampler, + OperatorBase, + ExpectationBase, +) +from qiskit.opflow.gradients.circuit_gradients import LinComb +from qiskit.utils import QuantumInstance +from .real_variational_principle import ( + RealVariationalPrinciple, +) + + +class RealMcLachlanPrinciple(RealVariationalPrinciple): + """Class for a Real McLachlan's Variational Principle. It aims to minimize the distance + between both sides of the Schrödinger equation with a quantum state given as a parametrized + trial state. The principle leads to a system of linear equations handled by a linear solver. + The real variant means that we consider real time dynamics. + """ + + def __init__( + self, + qfi_method: Union[str, CircuitQFI] = "lin_comb_full", + ) -> None: + """ + Args: + qfi_method: The method used to compute the QFI. Can be either + ``'lin_comb_full'`` or ``'overlap_block_diag'`` or ``'overlap_diag'`` or + ``CircuitQFI``. + """ + self._grad_method = LinComb(aux_meas_op=-Y) + self._energy_param = None + self._energy = None + + super().__init__(qfi_method) + + def evolution_grad( + self, + hamiltonian: OperatorBase, + ansatz: QuantumCircuit, + circuit_sampler: CircuitSampler, + param_dict: Dict[Parameter, complex], + bind_params: List[Parameter], + gradient_params: List[Parameter], + param_values: List[complex], + expectation: Optional[ExpectationBase] = None, + quantum_instance: Optional[QuantumInstance] = None, + ) -> np.ndarray: + """ + Calculates an evolution gradient according to the rules of this variational principle. + + Args: + hamiltonian: Operator used for Variational Quantum Time Evolution. The operator may be + given either as a composed op consisting of a Hermitian observable and a + ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a ``ComboFn``. The + latter case enables the evaluation of a Quantum Natural Gradient. + ansatz: Quantum state in the form of a parametrized quantum circuit. + circuit_sampler: A circuit sampler. + param_dict: Dictionary which relates parameter values to the parameters in the ansatz. + bind_params: List of parameters that are supposed to be bound. + gradient_params: List of parameters with respect to which gradients should be computed. + param_values: Values of parameters to be bound. + expectation: An instance of ``ExpectationBase`` used for calculating an evolution + gradient. If ``None`` provided, a ``PauliExpectation`` is used. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + + Returns: + An evolution gradient. + """ + if self._evolution_gradient_callable is None: + self._energy_param = Parameter("alpha") + modified_hamiltonian = self._construct_expectation( + hamiltonian, ansatz, self._energy_param + ) + + self._evolution_gradient_callable = self._evolution_gradient.gradient_wrapper( + modified_hamiltonian, + bind_params + [self._energy_param], + gradient_params, + quantum_instance, + expectation, + ) + + energy = StateFn(hamiltonian, is_measurement=True) @ StateFn(ansatz) + if expectation is None: + expectation = PauliExpectation() + self._energy = expectation.convert(energy) + + if circuit_sampler is not None: + energy = circuit_sampler.convert(self._energy, param_dict).eval() + else: + energy = self._energy.assign_parameters(param_dict).eval() + + param_values.append(real(energy)) + evolution_grad = 0.5 * self._evolution_gradient_callable(param_values) + + # quick fix due to an error on opflow; to be addressed in a separate PR + evolution_grad = (-1) * evolution_grad + return evolution_grad + + @staticmethod + def _construct_expectation( + hamiltonian: OperatorBase, ansatz: QuantumCircuit, energy_param: Parameter + ) -> OperatorBase: + """ + Modifies a Hamiltonian according to the rules of this variational principle. + + Args: + hamiltonian: Operator used for Variational Quantum Time Evolution. The operator may be + given either as a composed op consisting of a Hermitian observable and a + ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a ``ComboFn``. The + latter case enables the evaluation of a Quantum Natural Gradient. + ansatz: Quantum state in the form of a parametrized quantum circuit. + energy_param: Parameter for energy correction. + + Returns: + An modified Hamiltonian composed with an ansatz. + """ + energy_term = I ^ hamiltonian.num_qubits + energy_term *= -1 + energy_term *= energy_param + modified_hamiltonian = SummedOp([hamiltonian, energy_term]).reduce() + return StateFn(modified_hamiltonian, is_measurement=True) @ StateFn(ansatz) diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/real_variational_principle.py b/qiskit/algorithms/evolvers/variational/variational_principles/real_variational_principle.py new file mode 100644 index 000000000000..881e1f3827c7 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/real_variational_principle.py @@ -0,0 +1,42 @@ +# 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. + +"""Class for a Real Variational Principle.""" + +from abc import ABC +from typing import Union + +from qiskit.opflow import ( + CircuitQFI, +) +from .variational_principle import ( + VariationalPrinciple, +) + + +class RealVariationalPrinciple(VariationalPrinciple, ABC): + """Class for a Real Variational Principle. The real variant means that we consider real time + dynamics.""" + + def __init__( + self, + qfi_method: Union[str, CircuitQFI] = "lin_comb_full", + ) -> None: + """ + Args: + qfi_method: The method used to compute the QFI. Can be either ``'lin_comb_full'`` or + ``'overlap_block_diag'`` or ``'overlap_diag'`` or ``CircuitQFI``. + """ + super().__init__( + qfi_method, + self._grad_method, + ) diff --git a/qiskit/algorithms/evolvers/variational/variational_principles/variational_principle.py b/qiskit/algorithms/evolvers/variational/variational_principles/variational_principle.py new file mode 100644 index 000000000000..d3d6cbc20b67 --- /dev/null +++ b/qiskit/algorithms/evolvers/variational/variational_principles/variational_principle.py @@ -0,0 +1,129 @@ +# 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. + +"""Class for a Variational Principle.""" + +from abc import ABC, abstractmethod +from typing import Union, List, Optional, Dict + +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.opflow import ( + CircuitQFI, + CircuitGradient, + QFI, + Gradient, + CircuitStateFn, + CircuitSampler, + OperatorBase, + ExpectationBase, +) +from qiskit.utils import QuantumInstance + + +class VariationalPrinciple(ABC): + """A Variational Principle class. It determines the time propagation of parameters in a + quantum state provided as a parametrized quantum circuit (ansatz).""" + + def __init__( + self, + qfi_method: Union[str, CircuitQFI] = "lin_comb_full", + grad_method: Union[str, CircuitGradient] = "lin_comb", + ) -> None: + """ + Args: + grad_method: The method used to compute the state gradient. Can be either + ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'`` or ``CircuitGradient``. + qfi_method: The method used to compute the QFI. Can be either + ``'lin_comb_full'`` or ``'overlap_block_diag'`` or ``'overlap_diag'`` or + ``CircuitQFI``. + """ + self._qfi_method = qfi_method + self.qfi = QFI(qfi_method) + self._grad_method = grad_method + self._evolution_gradient = Gradient(self._grad_method) + self._qfi_gradient_callable = None + self._evolution_gradient_callable = None + + def metric_tensor( + self, + ansatz: QuantumCircuit, + bind_params: List[Parameter], + gradient_params: List[Parameter], + param_values: List[complex], + expectation: Optional[ExpectationBase] = None, + quantum_instance: Optional[QuantumInstance] = None, + ) -> np.ndarray: + """ + Calculates a metric tensor according to the rules of this variational principle. + + Args: + ansatz: Quantum state in the form of a parametrized quantum circuit. + bind_params: List of parameters that are supposed to be bound. + gradient_params: List of parameters with respect to which gradients should be computed. + param_values: Values of parameters to be bound. + expectation: An instance of ``ExpectationBase`` used for calculating a metric tensor. + If ``None`` provided, a ``PauliExpectation`` is used. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + + Returns: + Metric tensor. + """ + if self._qfi_gradient_callable is None: + self._qfi_gradient_callable = self.qfi.gradient_wrapper( + CircuitStateFn(ansatz), bind_params, gradient_params, quantum_instance, expectation + ) + metric_tensor = 0.25 * self._qfi_gradient_callable(param_values) + + return metric_tensor + + @abstractmethod + def evolution_grad( + self, + hamiltonian: OperatorBase, + ansatz: QuantumCircuit, + circuit_sampler: CircuitSampler, + param_dict: Dict[Parameter, complex], + bind_params: List[Parameter], + gradient_params: List[Parameter], + param_values: List[complex], + expectation: Optional[ExpectationBase] = None, + quantum_instance: Optional[QuantumInstance] = None, + ) -> np.ndarray: + """ + Calculates an evolution gradient according to the rules of this variational principle. + + Args: + hamiltonian: Operator used for Variational Quantum Time Evolution. The operator may be + given either as a composed op consisting of a Hermitian observable and a + ``CircuitStateFn`` or a ``ListOp`` of a ``CircuitStateFn`` with a ``ComboFn``. The + latter case enables the evaluation of a Quantum Natural Gradient. + ansatz: Quantum state in the form of a parametrized quantum circuit. + circuit_sampler: A circuit sampler. + param_dict: Dictionary which relates parameter values to the parameters in the ansatz. + bind_params: List of parameters that are supposed to be bound. + gradient_params: List of parameters with respect to which gradients should be computed. + param_values: Values of parameters to be bound. + expectation: An instance of ``ExpectationBase`` used for calculating an evolution + gradient. If ``None`` provided, a ``PauliExpectation`` is used. + quantum_instance: Backend used to evaluate the quantum circuit outputs. If ``None`` + provided, everything will be evaluated based on matrix multiplication (which is + slow). + + Returns: + An evolution gradient. + """ + pass diff --git a/qiskit/opflow/gradients/derivative_base.py b/qiskit/opflow/gradients/derivative_base.py index 7eff1e4e57c8..1e1b3fadb5b7 100644 --- a/qiskit/opflow/gradients/derivative_base.py +++ b/qiskit/opflow/gradients/derivative_base.py @@ -105,7 +105,7 @@ def gradient_wrapper( """ from ..converters import CircuitSampler - if not grad_params: + if grad_params is None: grad_params = bind_params grad = self.convert(operator, grad_params) @@ -113,15 +113,18 @@ def gradient_wrapper( expectation = PauliExpectation() grad = expectation.convert(grad) + sampler = CircuitSampler(backend=backend) if backend is not None else None + def gradient_fn(p_values): p_values_dict = dict(zip(bind_params, p_values)) if not backend: converter = grad.assign_parameters(p_values_dict) return np.real(converter.eval()) else: - p_values_dict = {k: [v] for k, v in p_values_dict.items()} - converter = CircuitSampler(backend=backend).convert(grad, p_values_dict) - return np.real(converter.eval()[0]) + p_values_list = {k: [v] for k, v in p_values_dict.items()} + sampled = sampler.convert(grad, p_values_list) + fully_bound = sampled.bind_parameters(p_values_dict) + return np.real(fully_bound.eval()[0]) return gradient_fn diff --git a/releasenotes/notes/add-variational-quantum-time-evolution-112ffeaf62782fea.yaml b/releasenotes/notes/add-variational-quantum-time-evolution-112ffeaf62782fea.yaml new file mode 100644 index 000000000000..fc4d0fb891d3 --- /dev/null +++ b/releasenotes/notes/add-variational-quantum-time-evolution-112ffeaf62782fea.yaml @@ -0,0 +1,50 @@ +--- +features: + - | + Add algorithms for Variational Quantum Time Evolution that implement a new interface for + Quantum Time Evolution. The feature supports real (:class:`qiskit.algorithms.VarQRTE`.) and + imaginary (:class:`qiskit.algorithms.VarQITE`.) quantum time evolution according to a + variational principle passed. Each algorithm accepts a variational principle and the following + are provided: + :class:`qiskit.algorithms.evolvers.variational.ImaginaryMcLachlanPrinciple`, + :class:`qiskit.algorithms.evolvers.variational.RealMcLachlanPrinciple`, + :class:`qiskit.algorithms.evolvers.variational.RealTimeDependentPrinciple`. + Both algorithms require solving ODE equations and linear equations which is handled by classes + implemented in `qiskit.algorithms.evolvers.variational.solvers` module. + + .. code-block:: python + + from qiskit.algorithms import EvolutionProblem + from qiskit.algorithms import VarQITE + from qiskit import BasicAer + from qiskit.circuit.library import EfficientSU2 + from qiskit.opflow import SummedOp, I, Z, Y, X + from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, + ) + from qiskit.algorithms import EvolutionProblem + import numpy as np + + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ).reduce() + + ansatz = EfficientSU2(observable.num_qubits, reps=1) + parameters = ansatz.parameters + init_param_values = np.zeros(len(ansatz.parameters)) + for i in range(len(ansatz.parameters)): + init_param_values[i] = np.pi / 2 + param_dict = dict(zip(parameters, init_param_values)) + var_principle = ImaginaryMcLachlanPrinciple() + backend = BasicAer.get_backend("statevector_simulator") + time = 1 + evolution_problem = EvolutionProblem(observable, time) + var_qite = VarQITE(ansatz, var_principle, param_dict, quantum_instance=backend) + evolution_result = var_qite.evolve(evolution_problem) diff --git a/releasenotes/notes/fix-gradient-wrapper-2f9ab45941739044.yaml b/releasenotes/notes/fix-gradient-wrapper-2f9ab45941739044.yaml new file mode 100644 index 000000000000..07de4aa4eb0b --- /dev/null +++ b/releasenotes/notes/fix-gradient-wrapper-2f9ab45941739044.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed the following issues with the function + :func:`~qiskit.opflow.gradients.derivative_base.gradient_wrapper`: + - reusing a circuit sampler between the calls, + - binding nested parameters. diff --git a/test/python/algorithms/evolvers/test_evolution_problem.py b/test/python/algorithms/evolvers/test_evolution_problem.py index 0d1d18951039..d3dd2f7bc9b2 100644 --- a/test/python/algorithms/evolvers/test_evolution_problem.py +++ b/test/python/algorithms/evolvers/test_evolution_problem.py @@ -38,14 +38,14 @@ def test_init_default(self): expected_initial_state = One expected_aux_operators = None expected_t_param = None - expected_hamiltonian_value_dict = None + expected_param_value_dict = None self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) self.assertEqual(evo_problem.time, expected_time) self.assertEqual(evo_problem.initial_state, expected_initial_state) self.assertEqual(evo_problem.aux_operators, expected_aux_operators) self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.hamiltonian_value_dict, expected_hamiltonian_value_dict) + self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) def test_init_all(self): """Tests that all fields are initialized correctly.""" @@ -54,7 +54,7 @@ def test_init_all(self): time = 2 initial_state = One aux_operators = [X, Y] - hamiltonian_value_dict = {t_parameter: 3.2} + param_value_dict = {t_parameter: 3.2} evo_problem = EvolutionProblem( hamiltonian, @@ -62,7 +62,7 @@ def test_init_all(self): initial_state, aux_operators, t_param=t_parameter, - hamiltonian_value_dict=hamiltonian_value_dict, + param_value_dict=param_value_dict, ) expected_hamiltonian = Y + t_parameter * Z @@ -70,14 +70,14 @@ def test_init_all(self): expected_initial_state = One expected_aux_operators = [X, Y] expected_t_param = t_parameter - expected_hamiltonian_value_dict = {t_parameter: 3.2} + expected_param_value_dict = {t_parameter: 3.2} self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) self.assertEqual(evo_problem.time, expected_time) self.assertEqual(evo_problem.initial_state, expected_initial_state) self.assertEqual(evo_problem.aux_operators, expected_aux_operators) self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.hamiltonian_value_dict, expected_hamiltonian_value_dict) + self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) @data([Y, -1, One], [Y, -1.2, One], [Y, 0, One]) @unpack @@ -93,27 +93,21 @@ def test_validate_params(self): 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 - ) + evolution_problem = EvolutionProblem(hamiltonian, 2, Zero, param_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 - ) + evolution_problem = EvolutionProblem(hamiltonian, 2, Zero, param_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 - ) + evolution_problem = EvolutionProblem(hamiltonian, 2, Zero, param_value_dict=param_dict) with assert_raises(ValueError): evolution_problem.validate_params() diff --git a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py index 7baad84fe59b..fe53f929f5f6 100644 --- a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py +++ b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py @@ -185,7 +185,7 @@ def test_trotter_qrte_trotter_two_qubits_with_params(self): 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 + operator, time, initial_state, param_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)) @@ -222,7 +222,7 @@ def test_trotter_qrte_qdrift(self, initial_state, expected_state): @data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None)) @unpack - def test_trotter_qrte_trotter_errors(self, t_param, hamiltonian_value_dict): + def test_trotter_qrte_trotter_errors(self, t_param, param_value_dict): """Test TrotterQRTE with raising errors.""" operator = X * Parameter("t") + Z initial_state = Zero @@ -235,7 +235,7 @@ def test_trotter_qrte_trotter_errors(self, t_param, hamiltonian_value_dict): time, initial_state, t_param=t_param, - hamiltonian_value_dict=hamiltonian_value_dict, + param_value_dict=param_value_dict, ) _ = trotter_qrte.evolve(evolution_problem) diff --git a/test/python/algorithms/evolvers/variational/__init__.py b/test/python/algorithms/evolvers/variational/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/solvers/__init__.py b/test/python/algorithms/evolvers/variational/solvers/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/solvers/expected_results/__init__.py b/test/python/algorithms/evolvers/variational/solvers/expected_results/__init__.py new file mode 100644 index 000000000000..9c3165f57a2a --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/expected_results/__init__.py @@ -0,0 +1,12 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py b/test/python/algorithms/evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py new file mode 100644 index 000000000000..c6dcf903673f --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py @@ -0,0 +1,182 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" +expected_metric_res_1 = [ + [ + 2.50000000e-01 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + -2.77500000e-17 + 0.0j, + 4.85000000e-17 + 0.0j, + 4.77630626e-32 + 0.0j, + ], + [ + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + 4.85334346e-32 + 0.0j, + 4.17500000e-17 + 0.0j, + ], + [ + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 1.38006319e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + ], + [ + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 1.38006319e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + ], + [ + -3.85185989e-33 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + 0.00000000e00 + 0.0j, + 4.85334346e-32 + 0.0j, + -7.00000000e-18 + 0.0j, + ], + [ + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + 4.85334346e-32 + 0.0j, + 4.17500000e-17 + 0.0j, + ], + [ + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 1.38006319e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + ], + [ + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 0.00000000e00 + 0.0j, + -1.38777878e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 1.38006319e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + ], + [ + 2.50000000e-01 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + -3.85185989e-33 + 0.0j, + -3.85185989e-33 + 0.0j, + -1.38777878e-17 + 0.0j, + -1.38777878e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + -2.77500000e-17 + 0.0j, + 4.85000000e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + ], + [ + -2.77500000e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + -7.00000000e-18 + 0.0j, + -7.00000000e-18 + 0.0j, + 0.00000000e00 + 0.0j, + 2.50000000e-01 + 0.0j, + -7.00000000e-18 + 0.0j, + -7.00000000e-18 + 0.0j, + -2.77500000e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + 0.00000000e00 + 0.0j, + 4.17500000e-17 + 0.0j, + ], + [ + 4.85000000e-17 + 0.0j, + 4.85334346e-32 + 0.0j, + 1.38006319e-17 + 0.0j, + 1.38006319e-17 + 0.0j, + 4.85334346e-32 + 0.0j, + 4.85334346e-32 + 0.0j, + 1.38006319e-17 + 0.0j, + 1.38006319e-17 + 0.0j, + 4.85000000e-17 + 0.0j, + 0.00000000e00 + 0.0j, + 2.50000000e-01 + 0.0j, + -2.77500000e-17 + 0.0j, + ], + [ + 4.77630626e-32 + 0.0j, + 4.17500000e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 4.17500000e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + -1.39493681e-17 + 0.0j, + -7.00000000e-18 + 0.0j, + 4.17500000e-17 + 0.0j, + -2.77500000e-17 + 0.0j, + 2.50000000e-01 + 0.0j, + ], +] diff --git a/test/python/algorithms/evolvers/variational/solvers/ode/__init__.py b/test/python/algorithms/evolvers/variational/solvers/ode/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/ode/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/solvers/ode/test_forward_euler_solver.py b/test/python/algorithms/evolvers/variational/solvers/ode/test_forward_euler_solver.py new file mode 100644 index 000000000000..08d13233c76e --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/ode/test_forward_euler_solver.py @@ -0,0 +1,47 @@ +# 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 Forward Euler solver.""" + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np +from ddt import ddt, data, unpack +from scipy.integrate import solve_ivp + +from qiskit.algorithms.evolvers.variational.solvers.ode.forward_euler_solver import ( + ForwardEulerSolver, +) + + +@ddt +class TestForwardEulerSolver(QiskitAlgorithmsTestCase): + """Test Forward Euler solver.""" + + @unpack + @data((4, 16), (16, 35.52713678800501), (320, 53.261108839604795)) + def test_solve(self, timesteps, expected_result): + """Test Forward Euler solver for a simple ODE.""" + + y0 = [1] + + # pylint: disable=unused-argument + def func(time, y): + return y + + t_span = [0.0, 4.0] + sol1 = solve_ivp(func, t_span, y0, method=ForwardEulerSolver, num_t_steps=timesteps) + np.testing.assert_equal(sol1.y[-1][-1], expected_result) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/solvers/ode/test_ode_function.py b/test/python/algorithms/evolvers/variational/solvers/ode/test_ode_function.py new file mode 100644 index 000000000000..1ef84204f2cc --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/ode/test_ode_function.py @@ -0,0 +1,165 @@ +# 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 ODE function generator.""" + +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np +from qiskit.algorithms.evolvers.variational.solvers.var_qte_linear_solver import ( + VarQTELinearSolver, +) +from qiskit.algorithms.evolvers.variational.solvers.ode.ode_function import ( + OdeFunction, +) +from qiskit import BasicAer +from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, +) +from qiskit.circuit import Parameter +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import ( + SummedOp, + X, + Y, + I, + Z, +) + + +class TestOdeFunctionGenerator(QiskitAlgorithmsTestCase): + """Test ODE function generator.""" + + def test_var_qte_ode_function(self): + """Test ODE function generator.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + + param_dict = {param: np.pi / 4 for param in parameters} + backend = BasicAer.get_backend("statevector_simulator") + + var_principle = ImaginaryMcLachlanPrinciple() + + t_param = None + linear_solver = None + linear_solver = VarQTELinearSolver( + var_principle, + observable, + ansatz, + parameters, + t_param, + linear_solver, + quantum_instance=backend, + ) + + time = 2 + ode_function_generator = OdeFunction( + linear_solver, error_calculator=None, t_param=None, param_dict=param_dict + ) + + qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) + + expected_qte_ode_function = [ + 0.442145, + -0.022081, + 0.106223, + -0.117468, + 0.251233, + 0.321256, + -0.062728, + -0.036209, + -0.509219, + -0.183459, + -0.050739, + -0.093163, + ] + + np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function) + + def test_var_qte_ode_function_time_param(self): + """Test ODE function generator with time param.""" + t_param = Parameter("t") + observable = SummedOp( + [ + 0.2252 * t_param * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + + param_dict = {param: np.pi / 4 for param in parameters} + backend = BasicAer.get_backend("statevector_simulator") + + var_principle = ImaginaryMcLachlanPrinciple() + + time = 2 + + linear_solver = None + linear_solver = VarQTELinearSolver( + var_principle, + observable, + ansatz, + parameters, + t_param, + linear_solver, + quantum_instance=backend, + ) + ode_function_generator = OdeFunction( + linear_solver, error_calculator=None, t_param=t_param, param_dict=param_dict + ) + + qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) + + expected_qte_ode_function = [ + 0.442145, + -0.022081, + 0.106223, + -0.117468, + 0.251233, + 0.321256, + -0.062728, + -0.036209, + -0.509219, + -0.183459, + -0.050739, + -0.093163, + ] + + np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function, decimal=5) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/solvers/ode/test_var_qte_ode_solver.py b/test/python/algorithms/evolvers/variational/solvers/ode/test_var_qte_ode_solver.py new file mode 100644 index 000000000000..5b39229ba502 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/ode/test_var_qte_ode_solver.py @@ -0,0 +1,136 @@ +# 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 solver of ODEs.""" + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +from ddt import ddt, data, unpack +import numpy as np +from qiskit.algorithms.evolvers.variational.solvers.ode.forward_euler_solver import ( + ForwardEulerSolver, +) +from qiskit.algorithms.evolvers.variational.solvers.var_qte_linear_solver import ( + VarQTELinearSolver, +) +from qiskit.algorithms.evolvers.variational.solvers.ode.var_qte_ode_solver import ( + VarQTEOdeSolver, +) +from qiskit.algorithms.evolvers.variational.solvers.ode.ode_function import ( + OdeFunction, +) +from qiskit import BasicAer +from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, +) +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import ( + SummedOp, + X, + Y, + I, + Z, +) + + +@ddt +class TestVarQTEOdeSolver(QiskitAlgorithmsTestCase): + """Test solver of ODEs.""" + + @data( + ( + "RK45", + [ + -0.30076755873631345, + -0.8032811383782005, + 1.1674108371914734e-15, + 3.2293849116821145e-16, + 2.541585055586039, + 1.155475184255733, + -2.966331417968169e-16, + 9.604292449638343e-17, + ], + ), + ( + ForwardEulerSolver, + [ + -3.2707e-01, + -8.0960e-01, + 3.4323e-16, + 8.9034e-17, + 2.5290e00, + 1.1563e00, + 3.0227e-16, + -2.2769e-16, + ], + ), + ) + @unpack + def test_run_no_backend(self, ode_solver, expected_result): + """Test ODE solver with no backend.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 1 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + + init_param_values = np.zeros(len(parameters)) + for i in range(ansatz.num_qubits): + init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 + + param_dict = dict(zip(parameters, init_param_values)) + + backend = BasicAer.get_backend("statevector_simulator") + + var_principle = ImaginaryMcLachlanPrinciple() + + time = 1 + + t_param = None + + linear_solver = None + linear_solver = VarQTELinearSolver( + var_principle, + observable, + ansatz, + parameters, + t_param, + linear_solver, + quantum_instance=backend, + ) + ode_function_generator = OdeFunction(linear_solver, None, param_dict, t_param) + + var_qte_ode_solver = VarQTEOdeSolver( + list(param_dict.values()), + ode_function_generator, + ode_solver=ode_solver, + num_timesteps=25, + ) + + result = var_qte_ode_solver.run(time) + + np.testing.assert_array_almost_equal(result, expected_result, decimal=4) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/solvers/test_varqte_linear_solver.py b/test/python/algorithms/evolvers/variational/solvers/test_varqte_linear_solver.py new file mode 100644 index 000000000000..c5442447bf9c --- /dev/null +++ b/test/python/algorithms/evolvers/variational/solvers/test_varqte_linear_solver.py @@ -0,0 +1,115 @@ +# 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 solver of linear equations.""" + +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from ddt import ddt, data +import numpy as np + +from qiskit import BasicAer +from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, +) +from qiskit.algorithms.evolvers.variational.solvers.var_qte_linear_solver import ( + VarQTELinearSolver, +) +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import SummedOp, X, Y, I, Z +from .expected_results.test_varqte_linear_solver_expected_1 import ( + expected_metric_res_1, +) + + +@ddt +class TestVarQTELinearSolver(QiskitAlgorithmsTestCase): + """Test solver of linear equations.""" + + @data(BasicAer.get_backend("statevector_simulator"), None) + def test_solve_lse(self, backend): + """Test SLE solver.""" + + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(ansatz.num_qubits): + init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 + + param_dict = dict(zip(parameters, init_param_values)) + + var_principle = ImaginaryMcLachlanPrinciple() + t_param = None + linear_solver = None + linear_solver = VarQTELinearSolver( + var_principle, + observable, + ansatz, + parameters, + t_param, + linear_solver, + quantum_instance=backend, + ) + + nat_grad_res, metric_res, grad_res = linear_solver.solve_lse(param_dict) + + expected_nat_grad_res = [ + 3.43500000e-01, + -2.89800000e-01, + 2.43575264e-16, + 1.31792695e-16, + -9.61200000e-01, + -2.89800000e-01, + 1.27493709e-17, + 1.12587456e-16, + 3.43500000e-01, + -2.89800000e-01, + 3.69914720e-17, + 1.95052083e-17, + ] + + expected_grad_res = [ + (0.17174999999999926 - 0j), + (-0.21735000000000085 + 0j), + (4.114902862895087e-17 - 0j), + (4.114902862895087e-17 - 0j), + (-0.24030000000000012 + 0j), + (-0.21735000000000085 + 0j), + (4.114902862895087e-17 - 0j), + (4.114902862895087e-17 - 0j), + (0.17174999999999918 - 0j), + (-0.21735000000000076 + 0j), + (1.7789936190837538e-17 - 0j), + (-8.319872568662832e-17 + 0j), + ] + + np.testing.assert_array_almost_equal(nat_grad_res, expected_nat_grad_res, decimal=4) + np.testing.assert_array_almost_equal(grad_res, expected_grad_res, decimal=4) + np.testing.assert_array_almost_equal(metric_res, expected_metric_res_1, decimal=4) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/test_var_qite.py b/test/python/algorithms/evolvers/variational/test_var_qite.py new file mode 100644 index 000000000000..6c4a26e13f8c --- /dev/null +++ b/test/python/algorithms/evolvers/variational/test_var_qite.py @@ -0,0 +1,287 @@ +# 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 Variational Quantum Imaginary Time Evolution algorithm.""" + +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from ddt import data, ddt +import numpy as np +from qiskit.test import slow_test +from qiskit.utils import algorithm_globals, QuantumInstance +from qiskit import BasicAer +from qiskit.algorithms import EvolutionProblem, VarQITE +from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, +) +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import ( + SummedOp, + X, + Y, + I, + Z, + ExpectationFactory, +) + + +@ddt +class TestVarQITE(QiskitAlgorithmsTestCase): + """Test Variational Quantum Imaginary Time Evolution algorithm.""" + + def setUp(self): + super().setUp() + self.seed = 11 + np.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=4000, + 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, + } + + self.backends_names = ["qi_qasm", "b_sv", "qi_sv"] + + @slow_test + def test_run_d_1_with_aux_ops(self): + """Test VarQITE for d = 1 and t = 1 with evaluating auxiliary operator and the Forward + Euler solver..""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + aux_ops = [X ^ X, Y ^ Z] + d = 1 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(len(parameters)): + init_param_values[i] = np.pi / 2 + init_param_values[0] = 1 + var_principle = ImaginaryMcLachlanPrinciple() + + param_dict = dict(zip(parameters, init_param_values)) + + time = 1 + + evolution_problem = EvolutionProblem(observable, time, aux_operators=aux_ops) + + thetas_expected_sv = [ + 1.03612467538419, + 1.91891042963193, + 2.81129500883365, + 2.78938736703301, + 2.2215151699331, + 1.61953721158502, + 2.23490753161058, + 1.97145113701782, + ] + + thetas_expected_qasm = [ + 1.03612467538419, + 1.91891042963193, + 2.81129500883365, + 2.78938736703301, + 2.2215151699331, + 1.61953721158502, + 2.23490753161058, + 1.97145113701782, + ] + + expected_aux_ops_evaluated_sv = [(-0.160899, 0.0), (0.26207, 0.0)] + expected_aux_ops_evaluated_qasm = [ + (-0.1765, 0.015563), + (0.2555, 0.015287), + ] + + for backend_name in self.backends_names: + with self.subTest(msg=f"Test {backend_name} backend."): + algorithm_globals.random_seed = self.seed + backend = self.backends_dict[backend_name] + expectation = ExpectationFactory.build( + operator=observable, + backend=backend, + ) + var_qite = VarQITE( + ansatz, + var_principle, + param_dict, + expectation=expectation, + num_timesteps=25, + quantum_instance=backend, + ) + evolution_result = var_qite.evolve(evolution_problem) + + evolved_state = evolution_result.evolved_state + aux_ops = evolution_result.aux_ops_evaluated + + parameter_values = evolved_state.data[0][0].params + + if backend_name == "qi_qasm": + thetas_expected = thetas_expected_qasm + expected_aux_ops = expected_aux_ops_evaluated_qasm + else: + thetas_expected = thetas_expected_sv + expected_aux_ops = expected_aux_ops_evaluated_sv + + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal( + float(parameter_value), thetas_expected[i], decimal=3 + ) + + np.testing.assert_array_almost_equal(aux_ops, expected_aux_ops) + + def test_run_d_1_t_7(self): + """Test VarQITE for d = 1 and t = 7 with RK45 ODE solver.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 1 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(len(parameters)): + init_param_values[i] = np.pi / 2 + init_param_values[0] = 1 + var_principle = ImaginaryMcLachlanPrinciple() + + backend = BasicAer.get_backend("statevector_simulator") + + time = 7 + var_qite = VarQITE( + ansatz, + var_principle, + init_param_values, + ode_solver="RK45", + num_timesteps=25, + quantum_instance=backend, + ) + + thetas_expected = [ + 0.828917365718767, + 1.88481074798033, + 3.14111335991238, + 3.14125849601269, + 2.33768562678401, + 1.78670990729437, + 2.04214275514208, + 2.04009918594422, + ] + + self._test_helper(observable, thetas_expected, time, var_qite, 2) + + @slow_test + @data( + SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ), + 0.2252 * (I ^ I) + + 0.5716 * (Z ^ Z) + + 0.3435 * (I ^ Z) + + -0.4347 * (Z ^ I) + + 0.091 * (Y ^ Y) + + 0.091 * (X ^ X), + ) + def test_run_d_2(self, observable): + """Test VarQITE for d = 2 and t = 1 with RK45 ODE solver.""" + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(len(parameters)): + init_param_values[i] = np.pi / 4 + + var_principle = ImaginaryMcLachlanPrinciple() + + param_dict = dict(zip(parameters, init_param_values)) + + backend = BasicAer.get_backend("statevector_simulator") + + time = 1 + var_qite = VarQITE( + ansatz, + var_principle, + param_dict, + ode_solver="RK45", + num_timesteps=25, + quantum_instance=backend, + ) + + thetas_expected = [ + 1.29495364023786, + 1.08970061333559, + 0.667488228710748, + 0.500122687902944, + 1.4377736672043, + 1.22881086103085, + 0.729773048146251, + 1.01698854755226, + 0.050807780587492, + 0.294828474947149, + 0.839305697704923, + 0.663689581255428, + ] + + self._test_helper(observable, thetas_expected, time, var_qite, 4) + + def _test_helper(self, observable, thetas_expected, time, var_qite, decimal): + evolution_problem = EvolutionProblem(observable, time) + evolution_result = var_qite.evolve(evolution_problem) + evolved_state = evolution_result.evolved_state + + parameter_values = evolved_state.data[0][0].params + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal( + float(parameter_value), thetas_expected[i], decimal=decimal + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/test_var_qrte.py b/test/python/algorithms/evolvers/variational/test_var_qrte.py new file mode 100644 index 000000000000..22ab25394e14 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/test_var_qrte.py @@ -0,0 +1,234 @@ +# 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 Variational Quantum Real Time Evolution algorithm.""" + +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from ddt import data, ddt +import numpy as np +from qiskit.test import slow_test +from qiskit.utils import QuantumInstance, algorithm_globals +from qiskit.algorithms import EvolutionProblem, VarQRTE +from qiskit.algorithms.evolvers.variational import ( + RealMcLachlanPrinciple, +) +from qiskit import BasicAer +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import ( + SummedOp, + X, + Y, + I, + Z, + ExpectationFactory, +) + + +@ddt +class TestVarQRTE(QiskitAlgorithmsTestCase): + """Test Variational Quantum Real Time Evolution algorithm.""" + + def setUp(self): + super().setUp() + self.seed = 11 + np.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=4000, + 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, + } + + self.backends_names = ["qi_qasm", "b_sv", "qi_sv"] + + @slow_test + def test_run_d_1_with_aux_ops(self): + """Test VarQRTE for d = 1 and t = 0.1 with evaluating auxiliary operators and the Forward + Euler solver.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + aux_ops = [X ^ X, Y ^ Z] + d = 1 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(len(parameters)): + init_param_values[i] = np.pi / 2 + init_param_values[0] = 1 + var_principle = RealMcLachlanPrinciple() + + time = 0.1 + + evolution_problem = EvolutionProblem(observable, time, aux_operators=aux_ops) + + thetas_expected_sv = [ + 0.88967020378258, + 1.53740751016451, + 1.57076759018861, + 1.58893301221363, + 1.60100970594142, + 1.57008242207638, + 1.63791241090936, + 1.53741371076912, + ] + + thetas_expected_qasm = [ + 0.88967811203145, + 1.53745130248168, + 1.57206794045495, + 1.58901347342829, + 1.60101431615503, + 1.57138020823337, + 1.63796000651177, + 1.53742227084076, + ] + + expected_aux_ops_evaluated_sv = [(0.06675, 0.0), (0.772636, 0.0)] + + expected_aux_ops_evaluated_qasm = [ + (0.06450000000000006, 0.01577846435810532), + (0.7895000000000001, 0.009704248425303218), + ] + + for backend_name in self.backends_names: + with self.subTest(msg=f"Test {backend_name} backend."): + algorithm_globals.random_seed = self.seed + backend = self.backends_dict[backend_name] + expectation = ExpectationFactory.build( + operator=observable, + backend=backend, + ) + var_qrte = VarQRTE( + ansatz, + var_principle, + init_param_values, + expectation=expectation, + num_timesteps=25, + quantum_instance=backend, + ) + evolution_result = var_qrte.evolve(evolution_problem) + + evolved_state = evolution_result.evolved_state + aux_ops = evolution_result.aux_ops_evaluated + + parameter_values = evolved_state.data[0][0].params + if backend_name == "qi_qasm": + thetas_expected = thetas_expected_qasm + expected_aux_ops = expected_aux_ops_evaluated_qasm + else: + thetas_expected = thetas_expected_sv + expected_aux_ops = expected_aux_ops_evaluated_sv + + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal( + float(parameter_value), thetas_expected[i], decimal=3 + ) + np.testing.assert_array_almost_equal(aux_ops, expected_aux_ops) + + @slow_test + @data( + SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ), + 0.2252 * (I ^ I) + + 0.5716 * (Z ^ Z) + + 0.3435 * (I ^ Z) + + -0.4347 * (Z ^ I) + + 0.091 * (Y ^ Y) + + 0.091 * (X ^ X), + ) + def test_run_d_2(self, observable): + """Test VarQRTE for d = 2 and t = 1 with RK45 ODE solver.""" + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + parameters = list(ansatz.parameters) + init_param_values = np.zeros(len(parameters)) + for i in range(len(parameters)): + init_param_values[i] = np.pi / 4 + + var_principle = RealMcLachlanPrinciple() + + param_dict = dict(zip(parameters, init_param_values)) + + backend = BasicAer.get_backend("statevector_simulator") + + time = 1 + var_qrte = VarQRTE( + ansatz, + var_principle, + param_dict, + ode_solver="RK45", + num_timesteps=25, + quantum_instance=backend, + ) + + thetas_expected = [ + 0.348407744196573, + 0.919404626262464, + 1.18189219371626, + 0.771011177789998, + 0.734384256533924, + 0.965289520781899, + 1.14441687204195, + 1.17231927568571, + 1.03014771379412, + 0.867266309056347, + 0.699606368428206, + 0.610788576398685, + ] + + self._test_helper(observable, thetas_expected, time, var_qrte) + + def _test_helper(self, observable, thetas_expected, time, var_qrte): + evolution_problem = EvolutionProblem(observable, time) + evolution_result = var_qrte.evolve(evolution_problem) + evolved_state = evolution_result.evolved_state + + parameter_values = evolved_state.data[0][0].params + for i, parameter_value in enumerate(parameter_values): + np.testing.assert_almost_equal(float(parameter_value), thetas_expected[i], decimal=4) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/test_var_qte.py b/test/python/algorithms/evolvers/variational/test_var_qte.py new file mode 100644 index 000000000000..1083a1564f68 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/test_var_qte.py @@ -0,0 +1,78 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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 Variational Quantum Real Time Evolution algorithm.""" + +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from numpy.testing import assert_raises +from ddt import data, ddt +import numpy as np + +from qiskit.algorithms.evolvers.variational.var_qte import VarQTE +from qiskit.circuit import Parameter + + +@ddt +class TestVarQTE(QiskitAlgorithmsTestCase): + """Test Variational Quantum Time Evolution class methods.""" + + def setUp(self): + super().setUp() + self._parameters1 = [Parameter("a"), Parameter("b"), Parameter("c")] + + @data([1.4, 2, 3], np.asarray([1.4, 2, 3])) + def test_create_init_state_param_dict(self, param_values): + """Tests if a correct dictionary is created.""" + expected = dict(zip(self._parameters1, param_values)) + with self.subTest("Parameters values given as a list test."): + result = VarQTE._create_init_state_param_dict(param_values, self._parameters1) + np.testing.assert_equal(result, expected) + with self.subTest("Parameters values given as a dictionary test."): + result = VarQTE._create_init_state_param_dict( + dict(zip(self._parameters1, param_values)), self._parameters1 + ) + np.testing.assert_equal(result, expected) + with self.subTest("Parameters values given as a superset dictionary test."): + expected = dict( + zip( + [self._parameters1[0], self._parameters1[2]], [param_values[0], param_values[2]] + ) + ) + result = VarQTE._create_init_state_param_dict( + dict(zip(self._parameters1, param_values)), + [self._parameters1[0], self._parameters1[2]], + ) + np.testing.assert_equal(result, expected) + + @data([1.4, 2], np.asarray([1.4, 3]), {}, []) + def test_create_init_state_param_dict_errors_list(self, param_values): + """Tests if an error is raised.""" + with assert_raises(ValueError): + _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) + + @data([1.4, 2], np.asarray([1.4, 3])) + def test_create_init_state_param_dict_errors_subset(self, param_values): + """Tests if an error is raised if subset of parameters provided.""" + param_values_dict = dict(zip([self._parameters1[0], self._parameters1[2]], param_values)) + with assert_raises(ValueError): + _ = VarQTE._create_init_state_param_dict(param_values_dict, self._parameters1) + + @data(5, "s", Parameter("x")) + def test_create_init_state_param_dict_errors_type(self, param_values): + """Tests if an error is raised if wrong input type.""" + with assert_raises(TypeError): + _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/variational_principles/__init__.py b/test/python/algorithms/evolvers/variational/variational_principles/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/variational_principles/expected_results/__init__.py b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/__init__.py new file mode 100644 index 000000000000..9c3165f57a2a --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/__init__.py @@ -0,0 +1,12 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py new file mode 100644 index 000000000000..231cbac4dba4 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py @@ -0,0 +1,182 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" +expected_bound_metric_tensor_1 = [ + [ + 2.50000000e-01 + 0.0j, + 1.59600000e-33 + 0.0j, + 5.90075760e-18 + 0.0j, + -8.49242405e-19 + 0.0j, + 8.83883476e-02 + 0.0j, + 1.33253788e-17 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.40000000e-17 + 0.0j, + -1.41735435e-01 + 0.0j, + 3.12500000e-02 + 0.0j, + 1.00222087e-01 + 0.0j, + -3.12500000e-02 + 0.0j, + ], + [ + 1.59600000e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + 1.34350288e-17 + 0.0j, + 6.43502884e-18 + 0.0j, + -8.83883476e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + -8.45970869e-02 + 0.0j, + 7.54441738e-02 + 0.0j, + 1.48207521e-01 + 0.0j, + 2.00444174e-01 + 0.0j, + ], + [ + 5.90075760e-18 + 0.0j, + 1.34350288e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -4.41941738e-02 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.19638348e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + -5.14514565e-02 + 0.0j, + 6.89720869e-02 + 0.0j, + 1.04933262e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + ], + [ + -8.49242405e-19 + 0.0j, + 6.43502884e-18 + 0.0j, + -1.38777878e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + -4.41941738e-02 + 0.0j, + -6.25000000e-02 + 0.0j, + 3.12500000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + 5.14514565e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + 7.81250000e-03 + 0.0j, + 1.94162607e-02 + 0.0j, + ], + [ + 8.83883476e-02 + 0.0j, + -8.83883476e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + 2.34375000e-01 + 0.0j, + -1.10485435e-01 + 0.0j, + -2.02014565e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + 1.49547935e-02 + 0.0j, + -2.24896848e-02 + 0.0j, + -1.42172278e-03 + 0.0j, + -1.23822206e-01 + 0.0j, + ], + [ + 1.33253788e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + -6.25000000e-02 + 0.0j, + -1.10485435e-01 + 0.0j, + 2.18750000e-01 + 0.0j, + -2.68082618e-03 + 0.0j, + -1.59099026e-17 + 0.0j, + -1.57197815e-01 + 0.0j, + 2.53331304e-02 + 0.0j, + 9.82311963e-03 + 0.0j, + 1.06138957e-01 + 0.0j, + ], + [ + 6.25000000e-02 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.19638348e-01 + 0.0j, + 3.12500000e-02 + 0.0j, + -2.02014565e-02 + 0.0j, + -2.68082618e-03 + 0.0j, + 2.23881674e-01 + 0.0j, + 1.37944174e-01 + 0.0j, + -3.78033966e-02 + 0.0j, + 1.58423239e-01 + 0.0j, + 1.34535646e-01 + 0.0j, + -5.49651086e-02 + 0.0j, + ], + [ + 1.40000000e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + -4.41941738e-02 + 0.0j, + -1.59099026e-17 + 0.0j, + 1.37944174e-01 + 0.0j, + 2.50000000e-01 + 0.0j, + -2.10523539e-17 + 0.0j, + 1.15574269e-17 + 0.0j, + 9.75412607e-02 + 0.0j, + 5.71383476e-02 + 0.0j, + ], + [ + -1.41735435e-01 + 0.0j, + -8.45970869e-02 + 0.0j, + -5.14514565e-02 + 0.0j, + 5.14514565e-02 + 0.0j, + 1.49547935e-02 + 0.0j, + -1.57197815e-01 + 0.0j, + -3.78033966e-02 + 0.0j, + -2.10523539e-17 + 0.0j, + 1.95283753e-01 + 0.0j, + -3.82941440e-02 + 0.0j, + -6.11392595e-02 + 0.0j, + -4.51588288e-02 + 0.0j, + ], + [ + 3.12500000e-02 + 0.0j, + 7.54441738e-02 + 0.0j, + 6.89720869e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + -2.24896848e-02 + 0.0j, + 2.53331304e-02 + 0.0j, + 1.58423239e-01 + 0.0j, + 1.15574269e-17 + 0.0j, + -3.82941440e-02 + 0.0j, + 2.17629701e-01 + 0.0j, + 1.32431810e-01 + 0.0j, + -1.91961467e-02 + 0.0j, + ], + [ + 1.00222087e-01 + 0.0j, + 1.48207521e-01 + 0.0j, + 1.04933262e-02 + 0.0j, + 7.81250000e-03 + 0.0j, + -1.42172278e-03 + 0.0j, + 9.82311963e-03 + 0.0j, + 1.34535646e-01 + 0.0j, + 9.75412607e-02 + 0.0j, + -6.11392595e-02 + 0.0j, + 1.32431810e-01 + 0.0j, + 1.81683746e-01 + 0.0j, + 7.28902444e-02 + 0.0j, + ], + [ + -3.12500000e-02 + 0.0j, + 2.00444174e-01 + 0.0j, + -6.89720869e-02 + 0.0j, + 1.94162607e-02 + 0.0j, + -1.23822206e-01 + 0.0j, + 1.06138957e-01 + 0.0j, + -5.49651086e-02 + 0.0j, + 5.71383476e-02 + 0.0j, + -4.51588288e-02 + 0.0j, + -1.91961467e-02 + 0.0j, + 7.28902444e-02 + 0.0j, + 2.38616353e-01 + 0.0j, + ], +] diff --git a/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py new file mode 100644 index 000000000000..386e3196ea4e --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py @@ -0,0 +1,182 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" +expected_bound_metric_tensor_2 = [ + [ + 2.50000000e-01 + 0.0j, + 1.59600000e-33 + 0.0j, + 5.90075760e-18 + 0.0j, + -8.49242405e-19 + 0.0j, + 8.83883476e-02 + 0.0j, + 1.33253788e-17 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.40000000e-17 + 0.0j, + -1.41735435e-01 + 0.0j, + 3.12500000e-02 + 0.0j, + 1.00222087e-01 + 0.0j, + -3.12500000e-02 + 0.0j, + ], + [ + 1.59600000e-33 + 0.0j, + 2.50000000e-01 + 0.0j, + 1.34350288e-17 + 0.0j, + 6.43502884e-18 + 0.0j, + -8.83883476e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + -8.45970869e-02 + 0.0j, + 7.54441738e-02 + 0.0j, + 1.48207521e-01 + 0.0j, + 2.00444174e-01 + 0.0j, + ], + [ + 5.90075760e-18 + 0.0j, + 1.34350288e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + -1.38777878e-17 + 0.0j, + -4.41941738e-02 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.19638348e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + -5.14514565e-02 + 0.0j, + 6.89720869e-02 + 0.0j, + 1.04933262e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + ], + [ + -8.49242405e-19 + 0.0j, + 6.43502884e-18 + 0.0j, + -1.38777878e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + -4.41941738e-02 + 0.0j, + -6.25000000e-02 + 0.0j, + 3.12500000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + 5.14514565e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + 7.81250000e-03 + 0.0j, + 1.94162607e-02 + 0.0j, + ], + [ + 8.83883476e-02 + 0.0j, + -8.83883476e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + 2.34375000e-01 + 0.0j, + -1.10485435e-01 + 0.0j, + -2.02014565e-02 + 0.0j, + -4.41941738e-02 + 0.0j, + 1.49547935e-02 + 0.0j, + -2.24896848e-02 + 0.0j, + -1.42172278e-03 + 0.0j, + -1.23822206e-01 + 0.0j, + ], + [ + 1.33253788e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + -6.25000000e-02 + 0.0j, + -1.10485435e-01 + 0.0j, + 2.18750000e-01 + 0.0j, + -2.68082618e-03 + 0.0j, + -1.59099026e-17 + 0.0j, + -1.57197815e-01 + 0.0j, + 2.53331304e-02 + 0.0j, + 9.82311963e-03 + 0.0j, + 1.06138957e-01 + 0.0j, + ], + [ + 6.25000000e-02 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.19638348e-01 + 0.0j, + 3.12500000e-02 + 0.0j, + -2.02014565e-02 + 0.0j, + -2.68082618e-03 + 0.0j, + 2.23881674e-01 + 0.0j, + 1.37944174e-01 + 0.0j, + -3.78033966e-02 + 0.0j, + 1.58423239e-01 + 0.0j, + 1.34535646e-01 + 0.0j, + -5.49651086e-02 + 0.0j, + ], + [ + 1.40000000e-17 + 0.0j, + 1.25000000e-01 + 0.0j, + 6.25000000e-02 + 0.0j, + 1.25000000e-01 + 0.0j, + -4.41941738e-02 + 0.0j, + -1.59099026e-17 + 0.0j, + 1.37944174e-01 + 0.0j, + 2.50000000e-01 + 0.0j, + -2.10523539e-17 + 0.0j, + 1.15574269e-17 + 0.0j, + 9.75412607e-02 + 0.0j, + 5.71383476e-02 + 0.0j, + ], + [ + -1.41735435e-01 + 0.0j, + -8.45970869e-02 + 0.0j, + -5.14514565e-02 + 0.0j, + 5.14514565e-02 + 0.0j, + 1.49547935e-02 + 0.0j, + -1.57197815e-01 + 0.0j, + -3.78033966e-02 + 0.0j, + -2.10523539e-17 + 0.0j, + 1.95283753e-01 + 0.0j, + -3.82941440e-02 + 0.0j, + -6.11392595e-02 + 0.0j, + -4.51588288e-02 + 0.0j, + ], + [ + 3.12500000e-02 + 0.0j, + 7.54441738e-02 + 0.0j, + 6.89720869e-02 + 0.0j, + -6.89720869e-02 + 0.0j, + -2.24896848e-02 + 0.0j, + 2.53331304e-02 + 0.0j, + 1.58423239e-01 + 0.0j, + 1.15574269e-17 + 0.0j, + -3.82941440e-02 + 0.0j, + 2.17629701e-01 + 0.0j, + 1.32431810e-01 + 0.0j, + -1.91961467e-02 + 0.0j, + ], + [ + 1.00222087e-01 + 0.0j, + 1.48207521e-01 + 0.0j, + 1.04933262e-02 + 0.0j, + 7.81250000e-03 + 0.0j, + -1.42172278e-03 + 0.0j, + 9.82311963e-03 + 0.0j, + 1.34535646e-01 + 0.0j, + 9.75412607e-02 + 0.0j, + -6.11392595e-02 + 0.0j, + 1.32431810e-01 + 0.0j, + 1.81683746e-01 + 0.0j, + 7.28902444e-02 + 0.0j, + ], + [ + -3.12500000e-02 + 0.0j, + 2.00444174e-01 + 0.0j, + -6.89720869e-02 + 0.0j, + 1.94162607e-02 + 0.0j, + -1.23822206e-01 + 0.0j, + 1.06138957e-01 + 0.0j, + -5.49651086e-02 + 0.0j, + 5.71383476e-02 + 0.0j, + -4.51588288e-02 + 0.0j, + -1.91961467e-02 + 0.0j, + 7.28902444e-02 + 0.0j, + 2.38616353e-01 + 0.0j, + ], +] diff --git a/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py new file mode 100644 index 000000000000..5c295c0c6f2a --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py @@ -0,0 +1,182 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. +"""Stores expected results that are lengthy.""" +expected_bound_metric_tensor_3 = [ + [ + -1.21000000e-34 + 0.00e00j, + 1.21000000e-34 + 2.50e-19j, + 1.76776695e-01 - 1.00e-18j, + -1.40000000e-17 + 0.00e00j, + -6.25000000e-02 + 0.00e00j, + 8.83883476e-02 - 1.25e-18j, + 1.69194174e-01 + 2.25e-18j, + 8.83883476e-02 - 2.50e-19j, + -7.27633476e-02 + 0.00e00j, + 9.75412607e-02 + 7.50e-19j, + 1.48398042e-02 - 1.75e-18j, + -9.75412607e-02 + 3.75e-18j, + ], + [ + 1.21000000e-34 + 2.50e-19j, + -1.21000000e-34 + 0.00e00j, + 1.10000000e-34 + 2.75e-18j, + 1.76776695e-01 - 2.25e-18j, + -6.25000000e-02 + 0.00e00j, + -8.83883476e-02 + 4.00e-18j, + 4.41941738e-02 - 1.25e-18j, + 1.76776695e-01 - 2.50e-19j, + 7.27633476e-02 - 7.50e-19j, + -9.75412607e-02 - 7.50e-19j, + 1.10485435e-02 - 7.50e-19j, + 2.74587393e-02 + 2.50e-19j, + ], + [ + 1.76776695e-01 - 1.00e-18j, + 1.10000000e-34 + 2.75e-18j, + -1.25000000e-01 + 0.00e00j, + -1.25000000e-01 + 0.00e00j, + -1.06694174e-01 + 1.25e-18j, + -6.25000000e-02 + 1.75e-18j, + -1.01332521e-01 + 7.50e-19j, + 4.67500000e-17 - 7.50e-19j, + 1.75206304e-02 + 5.00e-19j, + -8.57075215e-02 - 1.00e-18j, + -1.63277304e-01 + 1.00e-18j, + -1.56250000e-02 + 0.00e00j, + ], + [ + -1.40000000e-17 + 0.00e00j, + 1.76776695e-01 - 2.25e-18j, + -1.25000000e-01 + 0.00e00j, + -1.25000000e-01 + 0.00e00j, + 1.83058262e-02 - 1.50e-18j, + -1.50888348e-01 - 1.50e-18j, + -1.01332521e-01 + 2.50e-19j, + -8.83883476e-02 - 1.00e-18j, + -2.28822827e-02 - 1.00e-18j, + -1.16957521e-01 + 1.00e-18j, + -1.97208130e-01 + 0.00e00j, + -1.79457521e-01 + 1.25e-18j, + ], + [ + -6.25000000e-02 + 0.00e00j, + -6.25000000e-02 + 0.00e00j, + -1.06694174e-01 + 1.25e-18j, + 1.83058262e-02 - 1.50e-18j, + -1.56250000e-02 + 0.00e00j, + -2.20970869e-02 - 2.00e-18j, + 1.48992717e-01 - 1.00e-18j, + 2.60000000e-17 - 1.50e-18j, + -6.69614673e-02 - 5.00e-19j, + 2.00051576e-01 + 5.00e-19j, + 1.13640168e-01 + 1.25e-18j, + -4.83780325e-02 - 1.00e-18j, + ], + [ + 8.83883476e-02 - 1.25e-18j, + -8.83883476e-02 + 4.00e-18j, + -6.25000000e-02 + 1.75e-18j, + -1.50888348e-01 - 1.50e-18j, + -2.20970869e-02 - 2.00e-18j, + -3.12500000e-02 + 0.00e00j, + -2.85691738e-02 + 4.25e-18j, + 1.76776695e-01 + 0.00e00j, + 5.52427173e-03 + 1.00e-18j, + -1.29346478e-01 + 5.00e-19j, + -4.81004238e-02 + 4.25e-18j, + 5.27918696e-02 + 2.50e-19j, + ], + [ + 1.69194174e-01 + 2.25e-18j, + 4.41941738e-02 - 1.25e-18j, + -1.01332521e-01 + 7.50e-19j, + -1.01332521e-01 + 2.50e-19j, + 1.48992717e-01 - 1.00e-18j, + -2.85691738e-02 + 4.25e-18j, + -2.61183262e-02 + 0.00e00j, + -6.88900000e-33 + 0.00e00j, + 6.62099510e-02 - 1.00e-18j, + -2.90767610e-02 + 1.75e-18j, + -1.24942505e-01 + 0.00e00j, + -1.72430217e-02 + 2.50e-19j, + ], + [ + 8.83883476e-02 - 2.50e-19j, + 1.76776695e-01 - 2.50e-19j, + 4.67500000e-17 - 7.50e-19j, + -8.83883476e-02 - 1.00e-18j, + 2.60000000e-17 - 1.50e-18j, + 1.76776695e-01 + 0.00e00j, + -6.88900000e-33 + 0.00e00j, + -6.88900000e-33 + 0.00e00j, + 1.79457521e-01 - 1.75e-18j, + -5.33470869e-02 + 2.00e-18j, + -9.56456304e-02 + 3.00e-18j, + -1.32582521e-01 + 2.50e-19j, + ], + [ + -7.27633476e-02 + 0.00e00j, + 7.27633476e-02 - 7.50e-19j, + 1.75206304e-02 + 5.00e-19j, + -2.28822827e-02 - 1.00e-18j, + -6.69614673e-02 - 5.00e-19j, + 5.52427173e-03 + 1.00e-18j, + 6.62099510e-02 - 1.00e-18j, + 1.79457521e-01 - 1.75e-18j, + -5.47162473e-02 + 0.00e00j, + -4.20854047e-02 + 4.00e-18j, + -7.75494553e-02 - 2.50e-18j, + -2.49573723e-02 + 7.50e-19j, + ], + [ + 9.75412607e-02 + 7.50e-19j, + -9.75412607e-02 - 7.50e-19j, + -8.57075215e-02 - 1.00e-18j, + -1.16957521e-01 + 1.00e-18j, + 2.00051576e-01 + 5.00e-19j, + -1.29346478e-01 + 5.00e-19j, + -2.90767610e-02 + 1.75e-18j, + -5.33470869e-02 + 2.00e-18j, + -4.20854047e-02 + 4.00e-18j, + -3.23702991e-02 + 0.00e00j, + -4.70257118e-02 + 0.00e00j, + 1.22539288e-01 - 2.25e-18j, + ], + [ + 1.48398042e-02 - 1.75e-18j, + 1.10485435e-02 - 7.50e-19j, + -1.63277304e-01 + 1.00e-18j, + -1.97208130e-01 + 0.00e00j, + 1.13640168e-01 + 1.25e-18j, + -4.81004238e-02 + 4.25e-18j, + -1.24942505e-01 + 0.00e00j, + -9.56456304e-02 + 3.00e-18j, + -7.75494553e-02 - 2.50e-18j, + -4.70257118e-02 + 0.00e00j, + -6.83162540e-02 + 0.00e00j, + -2.78870598e-02 + 0.00e00j, + ], + [ + -9.75412607e-02 + 3.75e-18j, + 2.74587393e-02 + 2.50e-19j, + -1.56250000e-02 + 0.00e00j, + -1.79457521e-01 + 1.25e-18j, + -4.83780325e-02 - 1.00e-18j, + 5.27918696e-02 + 2.50e-19j, + -1.72430217e-02 + 2.50e-19j, + -1.32582521e-01 + 2.50e-19j, + -2.49573723e-02 + 7.50e-19j, + 1.22539288e-01 - 2.25e-18j, + -2.78870598e-02 + 0.00e00j, + -1.13836467e-02 + 0.00e00j, + ], +] diff --git a/test/python/algorithms/evolvers/variational/variational_principles/imaginary/__init__.py b/test/python/algorithms/evolvers/variational/variational_principles/imaginary/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/imaginary/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py b/test/python/algorithms/evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py new file mode 100644 index 000000000000..5118a9a699a4 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py @@ -0,0 +1,111 @@ +# 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 imaginary McLachlan's variational principle.""" + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np + +from qiskit.algorithms.evolvers.variational import ( + ImaginaryMcLachlanPrinciple, +) +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import SummedOp, X, Y, I, Z +from ..expected_results.test_imaginary_mc_lachlan_variational_principle_expected1 import ( + expected_bound_metric_tensor_1, +) + + +class TestImaginaryMcLachlanPrinciple(QiskitAlgorithmsTestCase): + """Test imaginary McLachlan's variational principle.""" + + def test_calc_metric_tensor(self): + """Test calculating a metric tensor.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + param_dict = {param: np.pi / 4 for param in parameters} + var_principle = ImaginaryMcLachlanPrinciple() + + bound_metric_tensor = var_principle.metric_tensor( + ansatz, parameters, parameters, param_dict.values(), None, None + ) + + np.testing.assert_almost_equal(bound_metric_tensor, expected_bound_metric_tensor_1) + + def test_calc_calc_evolution_grad(self): + """Test calculating evolution gradient.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + param_dict = {param: np.pi / 4 for param in parameters} + var_principle = ImaginaryMcLachlanPrinciple() + + bound_evolution_grad = var_principle.evolution_grad( + observable, + ansatz, + None, + param_dict, + parameters, + parameters, + param_dict.values(), + None, + None, + ) + + expected_bound_evolution_grad = [ + (0.19308934095957098 - 1.4e-17j), + (0.007027674650099142 - 0j), + (0.03192524520091862 - 0j), + (-0.06810314606309673 - 1e-18j), + (0.07590371669521798 - 7e-18j), + (0.11891968269385343 + 1.5e-18j), + (-0.0012030273438232639 + 0j), + (-0.049885258804562266 + 1.8500000000000002e-17j), + (-0.20178860797540302 - 5e-19j), + (-0.0052269232310933195 + 1e-18j), + (0.022892905637005266 - 3e-18j), + (-0.022892905637005294 + 3.5e-18j), + ] + + np.testing.assert_almost_equal(bound_evolution_grad, expected_bound_evolution_grad) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/variational/variational_principles/real/__init__.py b/test/python/algorithms/evolvers/variational/variational_principles/real/__init__.py new file mode 100644 index 000000000000..b3ac36d2a6d9 --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/real/__init__.py @@ -0,0 +1,11 @@ +# 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. diff --git a/test/python/algorithms/evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py b/test/python/algorithms/evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py new file mode 100644 index 000000000000..13c126928bdb --- /dev/null +++ b/test/python/algorithms/evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py @@ -0,0 +1,114 @@ +# 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 real McLachlan's variational principle.""" + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +import numpy as np +from qiskit.algorithms.evolvers.variational import ( + RealMcLachlanPrinciple, +) +from qiskit.circuit.library import EfficientSU2 +from qiskit.opflow import SummedOp, X, Y, I, Z +from ..expected_results.test_imaginary_mc_lachlan_variational_principle_expected2 import ( + expected_bound_metric_tensor_2, +) + + +class TestRealMcLachlanPrinciple(QiskitAlgorithmsTestCase): + """Test real McLachlan's variational principle.""" + + def test_calc_calc_metric_tensor(self): + """Test calculating a metric tensor.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + param_dict = {param: np.pi / 4 for param in parameters} + var_principle = RealMcLachlanPrinciple() + + bound_metric_tensor = var_principle.metric_tensor( + ansatz, parameters, parameters, list(param_dict.values()), None, None + ) + + np.testing.assert_almost_equal( + bound_metric_tensor, expected_bound_metric_tensor_2, decimal=5 + ) + + def test_calc_evolution_grad(self): + """Test calculating evolution gradient.""" + observable = SummedOp( + [ + 0.2252 * (I ^ I), + 0.5716 * (Z ^ Z), + 0.3435 * (I ^ Z), + -0.4347 * (Z ^ I), + 0.091 * (Y ^ Y), + 0.091 * (X ^ X), + ] + ) + + d = 2 + ansatz = EfficientSU2(observable.num_qubits, reps=d) + + # Define a set of initial parameters + parameters = list(ansatz.parameters) + param_dict = {param: np.pi / 4 for param in parameters} + var_principle = RealMcLachlanPrinciple() + + bound_evolution_grad = var_principle.evolution_grad( + observable, + ansatz, + None, + param_dict, + parameters, + parameters, + list(param_dict.values()), + None, + None, + ) + + expected_bound_evolution_grad = [ + (-0.04514911474522546 + 4e-18j), + (0.0963123928027075 - 1.5e-18j), + (0.1365347823673539 - 7e-18j), + (0.004969316401057883 - 4.9999999999999996e-18j), + (-0.003843833929692342 - 4.999999999999998e-19j), + (0.07036988622493834 - 7e-18j), + (0.16560609099860682 - 3.5e-18j), + (0.16674183768051887 + 1e-18j), + (-0.03843296670360974 - 6e-18j), + (0.08891074158680243 - 6e-18j), + (0.06425681697616654 + 7e-18j), + (-0.03172376682078948 - 7e-18j), + ] + + np.testing.assert_almost_equal( + bound_evolution_grad, expected_bound_evolution_grad, decimal=5 + ) + + +if __name__ == "__main__": + unittest.main()