From 0fb378c19cce70392f433a23fc2b1635d792e825 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 24 Mar 2023 08:46:38 +0100 Subject: [PATCH] Improve ``Parameter`` handling in ``SparsePauliOp`` (Qiskit/qiskit-terra#9796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add reno * Add assign_parameters and parameter in init * add SPO.parameters and remove utils * fix ParameterValueType typehint * Update qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py Co-authored-by: Ikko Hamamura * remove trailing print * Elena's comments Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * fix line length --------- Co-authored-by: Ikko Hamamura Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- .../trotterization/trotter_qrte.py | 23 ++++--- .../solvers/var_qte_linear_solver.py | 9 +-- qiskit_algorithms/utils/assign_params.py | 62 ------------------- test/time_evolvers/test_trotter_qrte.py | 8 +-- 4 files changed, 20 insertions(+), 82 deletions(-) delete mode 100644 qiskit_algorithms/utils/assign_params.py diff --git a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py index 3e2c1787..cb43e297 100644 --- a/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py +++ b/qiskit_algorithms/time_evolvers/trotterization/trotter_qrte.py @@ -26,8 +26,6 @@ from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.synthesis import ProductFormula, LieTrotter -from qiskit.algorithms.utils.assign_params import _assign_parameters, _get_parameters - class TrotterQRTE(RealTimeEvolver): """Quantum Real Time Evolution using Trotterization. @@ -165,16 +163,25 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult "The time evolution problem contained ``aux_operators`` but no estimator was " "provided. The algorithm continues without calculating these quantities. " ) + + # ensure the hamiltonian is a sparse pauli op hamiltonian = evolution_problem.hamiltonian if not isinstance(hamiltonian, (Pauli, PauliSumOp, SparsePauliOp)): raise ValueError( - f"TrotterQRTE only accepts Pauli | PauliSumOp, {type(hamiltonian)} provided." + f"TrotterQRTE only accepts Pauli | PauliSumOp | SparsePauliOp, {type(hamiltonian)} " + "provided." ) + if isinstance(hamiltonian, PauliSumOp): + hamiltonian = hamiltonian.primitive * hamiltonian.coeff + elif isinstance(hamiltonian, Pauli): + hamiltonian = SparsePauliOp(hamiltonian) + t_param = evolution_problem.t_param - if t_param is not None and _get_parameters(hamiltonian.coeffs) != ParameterView([t_param]): + free_parameters = hamiltonian.parameters + if t_param is not None and free_parameters != ParameterView([t_param]): raise ValueError( - "Hamiltonian time parameter does not match evolution_problem.t_param " - "or contains multiple parameters" + f"Hamiltonian time parameters ({free_parameters}) do not match " + f"evolution_problem.t_param ({t_param})." ) # make sure PauliEvolutionGate does not implement more than one Trotter step @@ -213,9 +220,9 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult # evolution for next step if t_param is not None: time_value = (n + 1) * dt - bound_coeffs = _assign_parameters(hamiltonian.coeffs, [time_value]) + bound_hamiltonian = hamiltonian.assign_parameters([time_value]) single_step_evolution_gate = PauliEvolutionGate( - SparsePauliOp(hamiltonian.paulis, bound_coeffs), + bound_hamiltonian, dt, synthesis=self.product_formula, ) diff --git a/qiskit_algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py b/qiskit_algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py index 4c99d853..12ff0c56 100644 --- a/qiskit_algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py +++ b/qiskit_algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py @@ -23,8 +23,6 @@ from qiskit.quantum_info import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.algorithms.utils.assign_params import _assign_parameters - from ..variational_principles import VariationalPrinciple @@ -115,13 +113,12 @@ def solve_lse( if self._time_param is not None: if time_value is not None: - bound_params_array = _assign_parameters(self._hamiltonian.coeffs, [time_value]) - hamiltonian = SparsePauliOp(self._hamiltonian.paulis, bound_params_array) + hamiltonian = hamiltonian.assign_parameters([time_value]) else: raise ValueError( - f"Providing a time_value is required for time-dependant hamiltonians, " + "Providing a time_value is required for time-dependent hamiltonians, " f"but got time_value = {time_value}. " - f"Please provide a time_value to the solve_lse method." + "Please provide a time_value to the solve_lse method." ) evolution_grad_lse_rhs = self._var_principle.evolution_gradient( diff --git a/qiskit_algorithms/utils/assign_params.py b/qiskit_algorithms/utils/assign_params.py deleted file mode 100644 index 622d3f87..00000000 --- a/qiskit_algorithms/utils/assign_params.py +++ /dev/null @@ -1,62 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Helper class for assigning values to parameters.""" -from __future__ import annotations - -from collections.abc import Sequence - -import copy -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.circuit.parametertable import ParameterView - - -def _get_parameters(array: np.ndarray) -> ParameterView: - """Retrieves parameters from a numpy array as a ``ParameterView``.""" - ret = set() - for a in array: - if isinstance(a, ParameterExpression): - ret |= a.parameters - return ParameterView(ret) - - -def _assign_parameters( - array: np.ndarray, parameter_values: Sequence[float], inplace: bool = False -) -> np.ndarray: - """Binds ``ParameterExpression``s in a numpy array to provided values. - - Args: - array: array of ``ParameterExpression`` - parameter_values: array of values to bind to parameters - inplace: If ``False``, a copy of the array with the bound parameters is returned. - If True the array itself is modified. - - Returns: - A copy of the array with bound parameters, if ``inplace`` is False, otherwise None. - """ - if inplace: - bound_array = array - else: - bound_array = copy.deepcopy(array) - - parameter_dict = dict(zip(_get_parameters(bound_array), parameter_values)) - for i, a in enumerate(bound_array): - if isinstance(a, ParameterExpression): - for key in a.parameters & parameter_dict.keys(): - a = a.assign(key, parameter_dict[key]) - if not a.parameters: - a = complex(a) - bound_array[i] = a - - return None if inplace else bound_array diff --git a/test/time_evolvers/test_trotter_qrte.py b/test/time_evolvers/test_trotter_qrte.py index 38e0bb93..e5d07a97 100644 --- a/test/time_evolvers/test_trotter_qrte.py +++ b/test/time_evolvers/test_trotter_qrte.py @@ -20,7 +20,6 @@ from numpy.testing import assert_raises from qiskit.algorithms.time_evolvers import TimeEvolutionProblem, TrotterQRTE -from qiskit.algorithms.utils.assign_params import _assign_parameters from qiskit.primitives import Estimator from qiskit import QuantumCircuit from qiskit.circuit.library import ZGate @@ -245,11 +244,8 @@ def _get_expected_trotter_qrte(operator, time, num_timesteps, init_state, observ for n in range(num_timesteps): if t_param is not None: time_value = (n + 1) * dt - bound_coeffs = _assign_parameters(operator.coeffs, [time_value]) - ops = [ - Pauli(op).to_matrix() * np.real(coeff) - for op, coeff in SparsePauliOp(operator.paulis, bound_coeffs).to_list() - ] + bound = operator.assign_parameters([time_value]) + ops = [Pauli(op).to_matrix() * np.real(coeff) for op, coeff in bound.to_list()] for op in ops: psi = expm(-1j * op * dt).dot(psi) observable_results.append(