From 0529f92083297ce49afeb531948d4fdc9d851525 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 6 Feb 2023 15:44:10 +0100 Subject: [PATCH 01/15] Add parameter batching --- qiskit/algorithms/eigensolvers/vqd.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index 7567f206a8d1..557c983486c8 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 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 @@ -366,20 +366,26 @@ def _get_evaluate_energy( def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: + # handle broadcasting: ensure parameters is of shape [array, array, ...] + if len(parameters.shape) == 1: + parameters = np.reshape(parameters, (-1, num_parameters)).tolist() + batch_size = len(parameters) + estimator_job = self.estimator.run( - circuits=[self.ansatz], observables=[operator], parameter_values=[parameters] + batch_size * [self.ansatz], batch_size * [operator], parameters ) - total_cost = 0 + total_cost = np.zeros(batch_size) if step > 1: # compute overlap cost fidelity_job = self.fidelity.run( - [self.ansatz] * (step - 1), - prev_states, - [parameters] * (step - 1), + batch_size * [self.ansatz] * (step - 1), + batch_size * prev_states, + parameters * (step - 1), ) costs = fidelity_job.result().fidelities + costs = np.reshape(costs, (batch_size, -1)).T for state, cost in enumerate(costs): total_cost += np.real(betas[state] * cost) @@ -398,7 +404,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: self._eval_count += 1 self.callback(self._eval_count, params, value, meta, step) else: - self._eval_count += len(values) + self._eval_count += len(values) / batch_size return values if len(values) > 1 else values[0] From 4607134c7a9ad51ac81d1f6df7c2097fa2e6d0a7 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 6 Feb 2023 15:58:20 +0100 Subject: [PATCH 02/15] Add spsa to tests --- test/python/algorithms/eigensolvers/test_vqd.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index 95efa27f5968..f573fd63e10f 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -21,11 +21,7 @@ from qiskit import QuantumCircuit from qiskit.algorithms.eigensolvers import VQD from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.optimizers import ( - COBYLA, - L_BFGS_B, - SLSQP, -) +from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP, SPSA from qiskit.algorithms.state_fidelities import ComputeUncompute from qiskit.circuit.library import TwoLocal, RealAmplitudes from qiskit.opflow import PauliSumOp @@ -107,9 +103,10 @@ def test_basic_operator(self, op): ) np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6) - def test_full_spectrum(self): + @data(L_BFGS_B(), SPSA()) + def test_full_spectrum(self, optimizer): """Test obtaining all eigenvalues.""" - vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B(), k=4) + vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=optimizer, k=4) result = vqd.compute_eigenvalues(H2_PAULI) np.testing.assert_array_almost_equal( result.eigenvalues.real, self.h2_energy_excited, decimal=2 @@ -230,6 +227,9 @@ def run_check(): with self.subTest("Optimizer replace"): vqd.optimizer = L_BFGS_B() run_check() + with self.subTest("SPSA replace"): + vqd.optimizer = SPSA() + run_check() @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) def test_aux_operators_list(self, op): From a0a36d54b5444a51d8a82b9394f2426a06520e10 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 6 Feb 2023 16:08:58 +0100 Subject: [PATCH 03/15] Add reno --- .../fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml diff --git a/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml new file mode 100644 index 000000000000..24c84dd582cf --- /dev/null +++ b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed a bug in the VQD algorithm (:class:`algorithms.eigensolvers.VQD`) where + the energy evaluation function could not process batches of parameters, making it + incompatible with :class:`qiskit.algorithms.optimizer.SPSA` -type optimizers. + See `#9500 `__. From ae7759873a7870008947f3ffd2eb0c1d493db297 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 8 Feb 2023 14:06:21 +0100 Subject: [PATCH 04/15] Remove batch size from evals --- qiskit/algorithms/eigensolvers/vqd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index 557c983486c8..637e9d56e556 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -388,6 +388,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: costs = np.reshape(costs, (batch_size, -1)).T for state, cost in enumerate(costs): + v = np.real(betas[state] * cost) total_cost += np.real(betas[state] * cost) try: @@ -404,7 +405,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: self._eval_count += 1 self.callback(self._eval_count, params, value, meta, step) else: - self._eval_count += len(values) / batch_size + self._eval_count += len(values) return values if len(values) > 1 else values[0] From f740ef44fbb963ee4fa2a6db9673bb0abf995340 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:39:59 +0100 Subject: [PATCH 05/15] Apply suggestions from code review --- qiskit/algorithms/eigensolvers/vqd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index 557c983486c8..c64ac837ea20 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -404,7 +404,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: self._eval_count += 1 self.callback(self._eval_count, params, value, meta, step) else: - self._eval_count += len(values) / batch_size + self._eval_count += len(values) return values if len(values) > 1 else values[0] From 6897e177d253cbf460866a6655e0651940518cfe Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:40:40 +0100 Subject: [PATCH 06/15] Apply suggestion from code review --- qiskit/algorithms/eigensolvers/vqd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index c64ac837ea20..6acb3be5466f 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -381,7 +381,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: fidelity_job = self.fidelity.run( batch_size * [self.ansatz] * (step - 1), batch_size * prev_states, - parameters * (step - 1), + np.tile(parameters, (step - 1, 1)), ) costs = fidelity_job.result().fidelities From 5857ceec033a09f36e3ec620f32d4f4e12a3c1cd Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 15 Feb 2023 17:59:56 +0100 Subject: [PATCH 07/15] Update tests --- qiskit/algorithms/eigensolvers/vqd.py | 7 ------- test/python/algorithms/eigensolvers/test_vqd.py | 10 +++++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index dbcd4b431d96..94632430882d 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -193,7 +193,6 @@ def compute_eigenvalues( operator: BaseOperator | PauliSumOp, aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, ) -> VQDResult: - super().compute_eigenvalues(operator, aux_operators) # this sets the size of the ansatz, so it must be called before the initial point @@ -226,7 +225,6 @@ def compute_eigenvalues( aux_operators = None if self.betas is None: - if isinstance(operator, PauliSumOp): operator = operator.coeff * operator.primitive @@ -254,7 +252,6 @@ def compute_eigenvalues( prev_states = [] for step in range(1, self.k + 1): - # update list of optimal circuits if step > 1: prev_states.append(self.ansatz.bind_parameters(result.optimal_points[-1])) @@ -365,7 +362,6 @@ def _get_evaluate_energy( self._check_operator_ansatz(operator) def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: - # handle broadcasting: ensure parameters is of shape [array, array, ...] if len(parameters.shape) == 1: parameters = np.reshape(parameters, (-1, num_parameters)).tolist() @@ -388,7 +384,6 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: costs = np.reshape(costs, (batch_size, -1)).T for state, cost in enumerate(costs): - v = np.real(betas[state] * cost) total_cost += np.real(betas[state] * cost) try: @@ -413,7 +408,6 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: @staticmethod def _build_vqd_result() -> VQDResult: - result = VQDResult() result.optimal_points = [] result.optimal_parameters = [] @@ -427,7 +421,6 @@ def _build_vqd_result() -> VQDResult: @staticmethod def _update_vqd_result(result, opt_result, eval_time, ansatz) -> VQDResult: - result.optimal_points.append(opt_result.x) result.optimal_parameters.append(dict(zip(ansatz.parameters, opt_result.x))) result.optimal_values.append(opt_result.fun) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index f573fd63e10f..c1b154d1af7c 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -103,10 +103,9 @@ def test_basic_operator(self, op): ) np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6) - @data(L_BFGS_B(), SPSA()) - def test_full_spectrum(self, optimizer): + def test_full_spectrum(self): """Test obtaining all eigenvalues.""" - vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=optimizer, k=4) + vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B(), k=4) result = vqd.compute_eigenvalues(H2_PAULI) np.testing.assert_array_almost_equal( result.eigenvalues.real, self.h2_energy_excited, decimal=2 @@ -227,6 +226,11 @@ def run_check(): with self.subTest("Optimizer replace"): vqd.optimizer = L_BFGS_B() run_check() + + with self.subTest("Batched optimizer replace"): + vqd.optimizer = SLSQP(maxiter=60, max_evals_grouped=10) + run_check() + with self.subTest("SPSA replace"): vqd.optimizer = SPSA() run_check() From a7ed09d014a928608273e7134546efce3c4fdf9a Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 15 Feb 2023 18:00:48 +0100 Subject: [PATCH 08/15] Delete Untitled.ipynb --- Untitled.ipynb | 199 ------------------------------------------------- 1 file changed, 199 deletions(-) delete mode 100644 Untitled.ipynb diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index 050524e5d9d6..000000000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,199 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 12, - "id": "dacfd688", - "metadata": {}, - "outputs": [], - "source": [ - "class BaseTokenizer:\n", - "\n", - " def __init__(self, str_token):\n", - " self.str_token = str_token\n", - "\n", - " def __iter__(self):\n", - " yield from self.str_token.split(\"-\")" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "5f3c6b38", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['28a2320b', 'fd3f', '4627', '9792', 'a2b38e3c46b0']" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tk = BaseTokenizer(\"28a2320b-fd3f-4627-9792-a2b38e3c46b0\")\n", - "list(tk)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "82f1870c", - "metadata": {}, - "outputs": [], - "source": [ - "class UpperIterableMixin:\n", - " def __iter__(self):\n", - " return map(str.upper, super().__iter__())\n", - "\n", - "\n", - "class Tokenizer(UpperIterableMixin, BaseTokenizer):\n", - " pass" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "98a4d189", - "metadata": {}, - "outputs": [], - "source": [ - "tk2 = Tokenizer(\"28a2320b-fd3f-4627-9792-a2b38e3c46b0\")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "53a5ba10", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['28A2320B', 'FD3F', '4627', '9792', 'A2B38E3C46B0']" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list(tk2)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "3ad34299", - "metadata": {}, - "outputs": [], - "source": [ - "class Sampler:\n", - "\n", - " def __init__(self, name):\n", - " self.name = name\n", - " \n", - " def hola(self, string):\n", - " print(string)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "63ca9530", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "123\n", - "holi\n" - ] - } - ], - "source": [ - "tk = BaseTokenizer(\"123\")\n", - "print(tk.name)\n", - "tk.hola(\"holi\")" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "049352fd", - "metadata": {}, - "outputs": [], - "source": [ - "class RetryMixin:\n", - " \n", - " def __init__(self, name):\n", - " super().__init__(\"bla\")\n", - "# self.name = name + name\n", - " \n", - " def hola(self, string):\n", - " super().hola(string)\n", - " print(\"adios\")\n", - "\n", - "\n", - "class RetrySampler(RetryMixin, Sampler):\n", - " pass" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "a2d551e1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bla\n", - "holi\n", - "adios\n" - ] - } - ], - "source": [ - "t = Tokenizer(\"abc\")\n", - "print(t.name)\n", - "t.hola(\"holi\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "effdad48", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 29506fdd69f7280344604b09165a1e1c1172bf7d Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 15 Feb 2023 18:02:19 +0100 Subject: [PATCH 09/15] Fix batch size SPSA --- test/python/algorithms/eigensolvers/test_vqd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index c1b154d1af7c..be645ee28f5f 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -232,7 +232,7 @@ def run_check(): run_check() with self.subTest("SPSA replace"): - vqd.optimizer = SPSA() + vqd.optimizer = SPSA(max_evals_grouped=4) run_check() @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) From afced2c9b039b4d12157040eb357b1a6bdc28a54 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 15 Feb 2023 18:12:37 +0100 Subject: [PATCH 10/15] Small fixes --- qiskit/algorithms/eigensolvers/vqd.py | 2 +- test/python/algorithms/eigensolvers/test_vqd.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index 94632430882d..96118fd71939 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -396,7 +396,7 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: if self.callback is not None: metadata = estimator_result.metadata - for params, value, meta in zip([parameters], values, metadata): + for params, value, meta in zip(parameters, values, metadata): self._eval_count += 1 self.callback(self._eval_count, params, value, meta, step) else: diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index be645ee28f5f..a263936a1161 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -232,7 +232,8 @@ def run_check(): run_check() with self.subTest("SPSA replace"): - vqd.optimizer = SPSA(max_evals_grouped=4) + vqd.optimizer = SPSA() + vqd.optimizer.set_max_evals_grouped(4) run_check() @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) From ad26101c7b237e52ca59a0beecdfa371e940d91d Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 15 Feb 2023 20:08:48 +0100 Subject: [PATCH 11/15] fix batching --- qiskit/algorithms/eigensolvers/vqd.py | 9 +++++---- test/python/algorithms/eigensolvers/test_vqd.py | 9 ++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py index 96118fd71939..4fc692bc79b8 100644 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ b/qiskit/algorithms/eigensolvers/vqd.py @@ -364,7 +364,7 @@ def _get_evaluate_energy( def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: # handle broadcasting: ensure parameters is of shape [array, array, ...] if len(parameters.shape) == 1: - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() + parameters = np.reshape(parameters, (-1, num_parameters)) batch_size = len(parameters) estimator_job = self.estimator.run( @@ -372,17 +372,18 @@ def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: ) total_cost = np.zeros(batch_size) + if step > 1: # compute overlap cost + batched_prev_states = [state for state in prev_states for _ in range(batch_size)] fidelity_job = self.fidelity.run( batch_size * [self.ansatz] * (step - 1), - batch_size * prev_states, + batched_prev_states, np.tile(parameters, (step - 1, 1)), ) - costs = fidelity_job.result().fidelities - costs = np.reshape(costs, (batch_size, -1)).T + costs = np.reshape(costs, (step - 1, -1)) for state, cost in enumerate(costs): total_cost += np.real(betas[state] * cost) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index a263936a1161..3e7d13043a89 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -200,9 +200,12 @@ def store_intermediate_result(eval_count, parameters, mean, metadata, step): np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_vqd_optimizer(self, op): + def test_vqd_optimizer(self): """Test running same VQD twice to re-use optimizer, then switch optimizer""" + + op = SparsePauliOp(["ZI", "IZ"], coeffs=[1, 0.5]) + energy_excited = [-1.5, -0.5, 0.5, 1.5] + vqd = VQD( estimator=self.estimator, fidelity=self.fidelity, @@ -215,7 +218,7 @@ def test_vqd_optimizer(self, op): def run_check(): result = vqd.compute_eigenvalues(operator=op) np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 + result.eigenvalues.real, energy_excited[:2], decimal=3 ) run_check() From d0f177535697ce74dfe674ddbeba6ea747c33cfa Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 15 Feb 2023 20:09:08 +0100 Subject: [PATCH 12/15] update test --- test/python/algorithms/eigensolvers/test_vqd.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index 3e7d13043a89..49596a944584 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -200,12 +200,10 @@ def store_intermediate_result(eval_count, parameters, mean, metadata, step): np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - def test_vqd_optimizer(self): + @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) + def test_vqd_optimizer(self, op): """Test running same VQD twice to re-use optimizer, then switch optimizer""" - op = SparsePauliOp(["ZI", "IZ"], coeffs=[1, 0.5]) - energy_excited = [-1.5, -0.5, 0.5, 1.5] - vqd = VQD( estimator=self.estimator, fidelity=self.fidelity, @@ -218,7 +216,7 @@ def test_vqd_optimizer(self): def run_check(): result = vqd.compute_eigenvalues(operator=op) np.testing.assert_array_almost_equal( - result.eigenvalues.real, energy_excited[:2], decimal=3 + result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 ) run_check() @@ -235,7 +233,7 @@ def run_check(): run_check() with self.subTest("SPSA replace"): - vqd.optimizer = SPSA() + vqd.optimizer = SPSA(maxiter=60) vqd.optimizer.set_max_evals_grouped(4) run_check() From 2afad32654fcd58ab5b52b5b44307e2af4199cf7 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 16 Feb 2023 10:27:11 +0100 Subject: [PATCH 13/15] Update reno --- .../notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml index 24c84dd582cf..9abf3e97dde3 100644 --- a/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml +++ b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml @@ -3,5 +3,5 @@ fixes: - | Fixed a bug in the VQD algorithm (:class:`algorithms.eigensolvers.VQD`) where the energy evaluation function could not process batches of parameters, making it - incompatible with :class:`qiskit.algorithms.optimizer.SPSA` -type optimizers. + incompatible with optimizers with ``max_evals_grouped>1``. See `#9500 `__. From 4d2ed7709e0022af445739355cb7b9f61e42be64 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 16 Feb 2023 10:52:54 +0100 Subject: [PATCH 14/15] Change SPSA test --- test/python/algorithms/eigensolvers/test_vqd.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py index 49596a944584..bd01311d6305 100644 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ b/test/python/algorithms/eigensolvers/test_vqd.py @@ -19,7 +19,7 @@ from ddt import data, ddt from qiskit import QuantumCircuit -from qiskit.algorithms.eigensolvers import VQD +from qiskit.algorithms.eigensolvers import VQD, VQDResult from qiskit.algorithms import AlgorithmError from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP, SPSA from qiskit.algorithms.state_fidelities import ComputeUncompute @@ -233,9 +233,11 @@ def run_check(): run_check() with self.subTest("SPSA replace"): - vqd.optimizer = SPSA(maxiter=60) - vqd.optimizer.set_max_evals_grouped(4) - run_check() + # SPSA takes too long to converge, so we will + # only check that it runs with no errors. + vqd.optimizer = SPSA(maxiter=5, learning_rate=0.01, perturbation=0.01) + result = vqd.compute_eigenvalues(operator=op) + self.assertIsInstance(result, VQDResult) @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) def test_aux_operators_list(self, op): From ac03e124ea32e2553e1a8a9e0f1b8dad571b014d Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:01:08 +0100 Subject: [PATCH 15/15] Apply reno suggestion Co-authored-by: Julien Gacon --- .../notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml index 9abf3e97dde3..268c2dc026ce 100644 --- a/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml +++ b/releasenotes/notes/fix-vqd-with-spsa-optimizers-9ed02b80f26e8abf.yaml @@ -1,7 +1,7 @@ --- fixes: - | - Fixed a bug in the VQD algorithm (:class:`algorithms.eigensolvers.VQD`) where + Fixed a bug in the :class:`~.eigensolvers.VQD` algorithm where the energy evaluation function could not process batches of parameters, making it incompatible with optimizers with ``max_evals_grouped>1``. See `#9500 `__.