Skip to content

Commit

Permalink
Allow access to UDMA optimizer history information (Qiskit#8695)
Browse files Browse the repository at this point in the history
* fix issue 8687 by moving history attribute tu public by a getter

* callback function

* tests

* add test and reno

* lint

---------

Co-authored-by: Julien Gacon <gaconju@gmail.com>
  • Loading branch information
2 people authored and king-p3nguin committed May 22, 2023
1 parent c991910 commit 886c659
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
20 changes: 19 additions & 1 deletion qiskit/algorithms/optimizers/umda.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# that they have been altered from the originals.

"""Univariate Marginal Distribution Algorithm (Estimation-of-Distribution-Algorithm)."""

from __future__ import annotations

from collections.abc import Callable
Expand Down Expand Up @@ -122,12 +123,21 @@ class UMDA(Optimizer):
ELITE_FACTOR = 0.4
STD_BOUND = 0.3

def __init__(self, maxiter: int = 100, size_gen: int = 20, alpha: float = 0.5) -> None:
def __init__(
self,
maxiter: int = 100,
size_gen: int = 20,
alpha: float = 0.5,
callback: Callable[[int, np.array, float], None] | None = None,
) -> None:
r"""
Args:
maxiter: Maximum number of iterations.
size_gen: Population size of each generation.
alpha: Percentage (0, 1] of the population to be selected as elite selection.
callback: A callback function passed information in each iteration step. The
information is, in this order: the number of function evaluations, the parameters,
the best function value in this iteration.
"""

self.size_gen = size_gen
Expand All @@ -148,6 +158,8 @@ def __init__(self, maxiter: int = 100, size_gen: int = 20, alpha: float = 0.5) -

self._n_variables: int | None = None

self.callback = callback

def _initialization(self) -> np.ndarray:
vector = np.zeros((4, self._n_variables))

Expand Down Expand Up @@ -243,6 +255,11 @@ def minimize(
if not_better_count >= self._dead_iter:
break

if self.callback is not None:
self.callback(
len(history) * self._size_gen, self._best_ind_global, self._best_cost_global
)

self._new_generation()

result.x = self._best_ind_global
Expand Down Expand Up @@ -321,6 +338,7 @@ def settings(self) -> dict[str, Any]:
"maxiter": self.maxiter,
"alpha": self.alpha,
"size_gen": self.size_gen,
"callback": self.callback,
}

def get_support_level(self):
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/umda-callback-eb644a49c5a9ad37.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features:
- |
Added the option to pass a callback to the :class:`.UMDA` optimizer, which allows
to keep track of the number of function evaluations, the current parameters, and the
best achieved function value.
30 changes: 29 additions & 1 deletion test/python/algorithms/optimizers/test_umda.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from test.python.algorithms import QiskitAlgorithmsTestCase

import numpy as np

from qiskit.algorithms.optimizers.umda import UMDA


Expand Down Expand Up @@ -45,14 +47,14 @@ def test_settings(self):
"maxiter": 100,
"alpha": 0.6,
"size_gen": 30,
"callback": None,
}

assert umda.settings == set_

def test_minimize(self):
"""optimize function test"""
from scipy.optimize import rosen
import numpy as np
from qiskit.utils import algorithm_globals

# UMDA is volatile so we need to set the seeds for the execution
Expand All @@ -66,3 +68,29 @@ def test_minimize(self):
assert len(res.x) == len(x_0)

np.testing.assert_array_almost_equal(res.x, [1.0] * len(x_0), decimal=2)

def test_callback(self):
"""Test the callback."""

def objective(x):
return np.linalg.norm(x) - 1

nfevs, parameters, fvals = [], [], []

def store_history(*args):
nfevs.append(args[0])
parameters.append(args[1])
fvals.append(args[2])

optimizer = UMDA(maxiter=1, callback=store_history)
_ = optimizer.minimize(objective, x0=np.arange(5))

self.assertEqual(len(nfevs), 1)
self.assertIsInstance(nfevs[0], int)

self.assertEqual(len(parameters), 1)
self.assertIsInstance(parameters[0], np.ndarray)
self.assertEqual(parameters[0].size, 5)

self.assertEqual(len(fvals), 1)
self.assertIsInstance(fvals[0], float)

0 comments on commit 886c659

Please sign in to comment.