From 002f93bbd59cd2fd2e558da8f5fff6d87f9c1588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20R=C3=B6seler?= Date: Wed, 20 Dec 2023 20:28:51 +0100 Subject: [PATCH 1/5] Fixed AQGD optimizer groups objective funtion calls by default --- .pylintdict | 1 + qiskit_algorithms/optimizers/aqgd.py | 14 ++++++++-- test/optimizers/test_optimizer_aqgd.py | 38 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/.pylintdict b/.pylintdict index 4e054f7d..4c468c9a 100644 --- a/.pylintdict +++ b/.pylintdict @@ -238,6 +238,7 @@ optimizer's optimizers otimes o'brien +parallelized param parameterizations parametrization diff --git a/qiskit_algorithms/optimizers/aqgd.py b/qiskit_algorithms/optimizers/aqgd.py index cf5695c8..065e1eb4 100644 --- a/qiskit_algorithms/optimizers/aqgd.py +++ b/qiskit_algorithms/optimizers/aqgd.py @@ -57,6 +57,7 @@ def __init__( momentum: float | list[float] = 0.25, param_tol: float = 1e-6, averaging: int = 10, + max_evals_grouped: int = 1, ) -> None: """ Performs Analytical Quantum Gradient Descent (AQGD) with Epochs. @@ -98,6 +99,7 @@ def __init__( self._param_tol = param_tol self._tol = tol self._averaging = averaging + self.set_max_evals_grouped(max_evals_grouped) # state self._avg_objval: float | None = None @@ -156,7 +158,15 @@ def _compute_objective_fn_and_gradient( ) # Evaluate, # reshaping to flatten, as expected by objective function - values = np.array(obj(param_sets_to_eval.reshape(-1))) + if self._max_evals_grouped > 1: + batches = [ + param_sets_to_eval[i : i + self._max_evals_grouped] + for i in range(0, len(param_sets_to_eval), self._max_evals_grouped) + ] + values = np.array(np.concatenate([obj(b) for b in batches])) + else: + batches = param_sets_to_eval + values = np.array([obj(b) for b in batches]) # Update number of objective function evaluations self._eval_count += 2 * num_params + 1 @@ -312,7 +322,6 @@ def minimize( iter_count = 0 logger.info("Initial Params: %s", params) - epoch = 0 converged = False for (eta, mom_coeff) in zip(self._eta, self._momenta_coeff): @@ -327,7 +336,6 @@ def minimize( converged = self._converged_parameter(params, self._param_tol) if converged: break - # Calculate objective function and estimate of analytical gradient if jac is None: objval, gradient = self._compute_objective_fn_and_gradient(params, fun) diff --git a/test/optimizers/test_optimizer_aqgd.py b/test/optimizers/test_optimizer_aqgd.py index c2136a95..0f6b1f7b 100644 --- a/test/optimizers/test_optimizer_aqgd.py +++ b/test/optimizers/test_optimizer_aqgd.py @@ -14,6 +14,7 @@ import unittest from test import QiskitAlgorithmsTestCase +import numpy as np from qiskit.circuit.library import RealAmplitudes from qiskit.primitives import Estimator from qiskit.quantum_info import SparsePauliOp @@ -93,6 +94,43 @@ def test_int_values(self): self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) + def test_max_grouped_evals(self): + """Tests max_grouped_evals parameter""" + # Test max_grouped_evals for an objective function that can be parallelized # + aqgd = AQGD(momentum=0.0, max_evals_grouped=2) + + vqe = VQE( + self.estimator, + ansatz=RealAmplitudes(), + optimizer=aqgd, + gradient=self.gradient, + ) + result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) + + self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) + aqgd.set_max_evals_grouped(1) + self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) + + # Test max_grouped_evals for an objective function that cannot be parallelized # + # Define the objective function (toy example for functionality) + def quadratic_objective(x: np.array) -> float: + # Check if only a single point as parameters is passed + if np.array(x).ndim != 1: + raise ValueError("The function expects a vector.") + + return x[0] ** 2 + x[1] ** 2 - 2 * x[0] * x[1] + + # Define initial point + x0 = np.array([1, 2.23]) + # Test max_evals_grouped raises no error for max_evals_grouped=1 + aqgd = AQGD(maxiter=100, max_evals_grouped=1) + x_new = aqgd.minimize(quadratic_objective, x0).x + self.assertAlmostEqual(sum(np.round(x_new / max(x_new), 7)), 0) + # Test max_evals_grouped raises an error for max_evals_grouped=2 + aqgd.set_max_evals_grouped(2) + with self.assertRaises(ValueError): + aqgd.minimize(quadratic_objective, x0) + if __name__ == "__main__": unittest.main() From a44cbcaf7188e37e50310b446461fa8c3ae1b3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20R=C3=B6seler?= Date: Wed, 20 Dec 2023 20:29:13 +0100 Subject: [PATCH 2/5] Added release node --- .../notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml diff --git a/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml new file mode 100644 index 00000000..f0ab33f5 --- /dev/null +++ b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + It has been fixed that the AQGD optimizer groups objective funtion calls by default. + The number of grouped evaluations can be controlled via the max_grouped_evals parameter. + From ec1a713b55c650c3938edb5c1be80f206af57943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20R=C3=B6seler?= Date: Wed, 20 Dec 2023 20:38:01 +0100 Subject: [PATCH 3/5] Added release node --- .../notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml index f0ab33f5..1d895128 100644 --- a/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml +++ b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml @@ -1,6 +1,6 @@ --- fixes: - | - It has been fixed that the AQGD optimizer groups objective funtion calls by default. + It has been fixed that the AQGD optimizer groups objective function calls by default. The number of grouped evaluations can be controlled via the max_grouped_evals parameter. From 70a5ce12b5d3713d568f7a998ab1bcab6fd5e6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20R=C3=B6seler?= Date: Thu, 21 Dec 2023 11:49:48 +0100 Subject: [PATCH 4/5] Fixed mypy test --- test/optimizers/test_optimizer_aqgd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/optimizers/test_optimizer_aqgd.py b/test/optimizers/test_optimizer_aqgd.py index 0f6b1f7b..83bbc8cb 100644 --- a/test/optimizers/test_optimizer_aqgd.py +++ b/test/optimizers/test_optimizer_aqgd.py @@ -113,7 +113,7 @@ def test_max_grouped_evals(self): # Test max_grouped_evals for an objective function that cannot be parallelized # # Define the objective function (toy example for functionality) - def quadratic_objective(x: np.array) -> float: + def quadratic_objective(x: np.ndarray) -> float: # Check if only a single point as parameters is passed if np.array(x).ndim != 1: raise ValueError("The function expects a vector.") From 1cace13b7ca03bac88b595ac56f0cc9faa816d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20R=C3=B6seler?= Date: Wed, 27 Dec 2023 10:40:53 +0100 Subject: [PATCH 5/5] Add documentation + splitted aqgd_test --- .pylintdict | 5 +++++ qiskit_algorithms/optimizers/aqgd.py | 1 + ...gd_max_grouped_evals-fbe108c005a9b7ac.yaml | 7 +++++-- test/optimizers/test_optimizer_aqgd.py | 19 +++++++++++-------- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.pylintdict b/.pylintdict index 4c468c9a..0984ec35 100644 --- a/.pylintdict +++ b/.pylintdict @@ -31,6 +31,7 @@ bitstrings bloch boltzmann bool +boolean boyer brassard broyden @@ -238,6 +239,7 @@ optimizer's optimizers otimes o'brien +parallelization parallelized param parameterizations @@ -256,6 +258,7 @@ postprocess powell pre preconditioner +prepend preprint preprocess preprocesses @@ -298,6 +301,7 @@ rightarrow robert rosen runarsson +runtime rz sanjiv sashank @@ -311,6 +315,7 @@ scikit scipy sdg seealso +serializable shanno skquant sle diff --git a/qiskit_algorithms/optimizers/aqgd.py b/qiskit_algorithms/optimizers/aqgd.py index 065e1eb4..0592a19b 100644 --- a/qiskit_algorithms/optimizers/aqgd.py +++ b/qiskit_algorithms/optimizers/aqgd.py @@ -74,6 +74,7 @@ def __init__( param_tol: Tolerance for change in norm of parameters. averaging: Length of window over which to average objective values for objective convergence criterion + max_evals_grouped: Max number of default gradient evaluations performed simultaneously. Raises: AlgorithmError: If the length of ``maxiter``, `momentum``, and ``eta`` is not the same. diff --git a/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml index 1d895128..9a0bcc92 100644 --- a/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml +++ b/releasenotes/notes/fix_aqgd_max_grouped_evals-fbe108c005a9b7ac.yaml @@ -1,6 +1,9 @@ --- fixes: - | - It has been fixed that the AQGD optimizer groups objective function calls by default. - The number of grouped evaluations can be controlled via the max_grouped_evals parameter. + Fixed the AQGD optimizer grouping objective function calls by default so that a single point is now passed to the + objective function. For algorithms that can handle more than one gradient evaluations in their objective function, + such as a VQE in the algorithms here, the number of grouped evaluations can be controlled via the max_grouped_evals + parameter. Grouped evaluations allows a list of points to be handed over so that they can potentially be assessed + more efficiently in a single job. diff --git a/test/optimizers/test_optimizer_aqgd.py b/test/optimizers/test_optimizer_aqgd.py index 83bbc8cb..df500648 100644 --- a/test/optimizers/test_optimizer_aqgd.py +++ b/test/optimizers/test_optimizer_aqgd.py @@ -15,6 +15,7 @@ import unittest from test import QiskitAlgorithmsTestCase import numpy as np +from ddt import ddt, data from qiskit.circuit.library import RealAmplitudes from qiskit.primitives import Estimator from qiskit.quantum_info import SparsePauliOp @@ -27,6 +28,7 @@ from qiskit_algorithms.utils import algorithm_globals +@ddt class TestOptimizerAQGD(QiskitAlgorithmsTestCase): """Test AQGD optimizer using RY for analytic gradient with VQE""" @@ -94,9 +96,9 @@ def test_int_values(self): self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - def test_max_grouped_evals(self): - """Tests max_grouped_evals parameter""" - # Test max_grouped_evals for an objective function that can be parallelized # + @data(1, 2, 3) # Values for max_grouped_evals + def test_max_grouped_evals_parallelizable(self, max_grouped_evals): + """Tests max_grouped_evals for an objective function that can be parallelized""" aqgd = AQGD(momentum=0.0, max_evals_grouped=2) vqe = VQE( @@ -105,13 +107,14 @@ def test_max_grouped_evals(self): optimizer=aqgd, gradient=self.gradient, ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - aqgd.set_max_evals_grouped(1) - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) + with self.subTest(max_grouped_evals=max_grouped_evals): + aqgd.set_max_evals_grouped(max_grouped_evals) + result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) + self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - # Test max_grouped_evals for an objective function that cannot be parallelized # + def test_max_grouped_evals_non_parallelizable(self): + """Tests max_grouped_evals for an objective function that cannot be parallelized""" # Define the objective function (toy example for functionality) def quadratic_objective(x: np.ndarray) -> float: # Check if only a single point as parameters is passed