Repository: facebookresearch/nevergrad

Issue:
ParametrizedBO with initialization=None raises StopIteration exception
## Steps to reproduce

  1. Use ParametrizedBO as optimizer with initialization=None (the default), see relevant code.

## Observed Results

```
Traceback (most recent call last):
  File ".\lib\site-packages\nevergrad\optimization\optimizerlib.py", line 1457, in _internal_ask_candidate
    x_probe = next(self.bo._queue)
  File ".\lib\site-packages\bayes_opt\bayesian_optimization.py", line 25, in __next__
    raise StopIteration("Queue is empty, no more objects to retrieve.")
StopIteration: Queue is empty, no more objects to retrieve.
```

## Expected Results

  Perhaps it should not raise a StopIteration exception because the initialization default value is None in the first place.

If initialization is either of ["random", "Hammersley", "LHS"] then there is no issue.

I discovered this issue in my [game parameter optimizer](https://github.com/fsmosca/Lakas) using nevergrad.

Perhaps don't use [None](https://github.com/facebookresearch/nevergrad/blob/d2bca44b1d50e30c5d96d3342a4cab505b82e429/nevergrad/optimization/optimizerlib.py#L1516) as default value.

## Relevant Code

  ```python
 import nevergrad as ng


def lossf(**x):
    a = x['a']
    return -a ** 2 + 1


arg = {'a': ng.p.Scalar(init=4, lower=1, upper=10).set_integer_casting()}
instrum = ng.p.Instrumentation(**arg)

gp_param = {'alpha': 1e-3, 'normalize_y': True,
            'n_restarts_optimizer': 5, 'random_state': None}

# initialization can be ["random", "Hammersley", "LHS"], default=None
my_opt = ng.optimizers.ParametrizedBO(gp_parameters=gp_param, initialization=None)
optimizer = my_opt(parametrization=instrum, budget=10)

recommendation = optimizer.minimize(lossf)
print(recommendation.value)
  ```

## Nevergrad
version: 0.4.2.post1




Discussion:

Resolving patch:
diff --git a/nevergrad/functions/powersystems/core.py b/nevergrad/functions/powersystems/core.py
--- a/nevergrad/functions/powersystems/core.py
+++ b/nevergrad/functions/powersystems/core.py
@@ -6,8 +6,8 @@
 # This code is based on a code and ideas by Emmanuel Centeno and Antoine Moreau,
 # University Clermont Auvergne, CNRS, SIGMA Clermont, Institut Pascal
 
-from collections import Iterable
 import typing as tp
+from collections import abc
 from math import pi, cos, sin
 import matplotlib.pyplot as plt
 import numpy as np
@@ -35,7 +35,7 @@ def dimension(self) -> int:
         return sum([np.prod(l.shape) for l in self.layers])
 
     def set_parameters(self, ww: tp.Any) -> None:
-        w = [w for w in ww]
+        w = list(ww)
         assert isinstance(w[0], float)
         assert len(w) == self.dimension, f"length = {len(w)} instead of {self.dimension}: {w}."
         for i in range(len(self.layers)):
@@ -126,12 +126,13 @@ def _simulate_power_system(self, *arrays: np.ndarray) -> float:
         failure_cost = self.failure_cost  # Cost of power demand which is not satisfied (equivalent to a expensive infinite thermal group).
         dam_agents = self.dam_agents
         # Deep, robust flattening of any nesting of iterables.
+
         def flatten(obj):
-             for i in obj:
-                 if isinstance(i, Iterable):
-                     yield from flatten(i)
-                 else:
-                     yield i
+            for i in obj:
+                if isinstance(i, abc.Iterable):
+                    yield from flatten(i)
+                else:
+                    yield i
         x = list(flatten(arrays))
         # Now applying:
         for a in dam_agents:
diff --git a/nevergrad/optimization/optimizerlib.py b/nevergrad/optimization/optimizerlib.py
--- a/nevergrad/optimization/optimizerlib.py
+++ b/nevergrad/optimization/optimizerlib.py
@@ -1432,30 +1432,33 @@ def bo(self) -> BayesianOptimization:
         if self._bo is None:
             bounds = {self._fake_function.key(i): (0.0, 1.0) for i in range(self.dimension)}
             self._bo = BayesianOptimization(self._fake_function, bounds, random_state=self._rng)
+            init_budget = max(2, int(np.sqrt(self.budget) if self.init_budget is None else self.init_budget))
             if self.gp_parameters is not None:
                 self._bo.set_gp_params(**self.gp_parameters)
             # init
             init = self.initialization
             if self.middle_point:
                 self._bo.probe([0.5] * self.dimension, lazy=True)
-            elif init is None:
-                self._bo._queue.add(self._bo._space.random_sample())
-            if init is not None:
-                init_budget = int(np.sqrt(self.budget) if self.init_budget is None else self.init_budget)
-                init_budget -= self.middle_point
-                if init_budget > 0:
-                    sampler = {"Hammersley": sequences.HammersleySampler, "LHS": sequences.LHSSampler, "random": sequences.RandomSampler}[
-                        init
-                    ](self.dimension, budget=init_budget, scrambling=(init == "Hammersley"), random_state=self._rng)
-                    for point in sampler:
-                        self._bo.probe(point, lazy=True)
+                init_budget -= 1
+            if init is not None and init_budget > 0:
+                sampler = {"Hammersley": sequences.HammersleySampler, "LHS": sequences.LHSSampler, "random": sequences.RandomSampler}[
+                    init
+                ](self.dimension, budget=init_budget, scrambling=(init == "Hammersley"), random_state=self._rng)
+                for k, point in enumerate(sampler):
+                    if not k and self.middle_point and np.linalg.norm(point - 0.5) < 1e-6:
+                        # resampling middle point, this is useless, let's redraw randomly
+                        point = self._bo._space.random_sample()
+                    self._bo.probe(point, lazy=True)
+            else:  # default
+                for _ in range(init_budget):
+                    self._bo.probe(self._bo._space.random_sample(), lazy=True)
         return self._bo
 
     def _internal_ask_candidate(self) -> p.Parameter:
         util = UtilityFunction(kind=self.utility_kind, kappa=self.utility_kappa, xi=self.utility_xi)
-        try:
+        if self.bo._queue:
             x_probe = next(self.bo._queue)
-        except StopIteration:
+        else:
             x_probe = self.bo.suggest(util)  # this is time consuming
             x_probe = [x_probe[self._fake_function.key(i)] for i in range(len(x_probe))]
         data = self._transform.backward(np.array(x_probe, copy=False))


Test patch:
diff --git a/nevergrad/optimization/test_optimizerlib.py b/nevergrad/optimization/test_optimizerlib.py
--- a/nevergrad/optimization/test_optimizerlib.py
+++ b/nevergrad/optimization/test_optimizerlib.py
@@ -68,14 +68,9 @@ def check_optimizer(
             warnings.filterwarnings("ignore", category=base.InefficientSettingsWarning)
             # some optimizers finish early
             warnings.filterwarnings("ignore", category=FinishedUnderlyingOptimizerWarning)
-            # skip BO error on windows (issue #506)
-            if "BO" in optimizer.name:
-                raise SkipTest("BO is currently not well supported")
-            if "Many" in optimizer.name:
-                raise SkipTest("When many algorithms are in the portfolio we are not good for small budget.")
             # now optimize :)
             candidate = optimizer.minimize(fitness)
-        if verify_value and "chain" not in str(optimizer_cls):
+        if verify_value:
             try:
                 np.testing.assert_array_almost_equal(candidate.args[0], optimum, decimal=1)
             except AssertionError as e:
@@ -131,11 +126,13 @@ def test_optimizers(name: str) -> None:
     if isinstance(optimizer_cls, base.ConfiguredOptimizer):
         assert any(hasattr(mod, name) for mod in (optlib, xpvariants))  # make sure registration matches name in optlib/xpvariants
         assert optimizer_cls.__class__(**optimizer_cls._config) == optimizer_cls, "Similar configuration are not equal"
-    verify = not optimizer_cls.one_shot and name not in SLOW and not any(x in name for x in ["BO", "Discrete"])
+    # some classes of optimizer are eigher slow or not good with small budgets:
+    nameparts = ["Many", "chain", "BO", "Discrete"]
+    verify = not optimizer_cls.one_shot and name not in SLOW and not any(x in name for x in nameparts)
     # the following context manager speeds up BO tests
     patched = partial(acq_max, n_warmup=10000, n_iter=2)
     with patch("bayes_opt.bayesian_optimization.acq_max", patched):
-        check_optimizer(optimizer_cls, budget=300 if "BO" not in name else 2, verify_value=verify)
+        check_optimizer(optimizer_cls, budget=300 if "BO" not in name else 4, verify_value=verify)
 
 
 class RecommendationKeeper:
@@ -181,14 +178,14 @@ def test_optimizers_suggest(name: str) -> None:  # pylint: disable=redefined-out
 # pylint: disable=redefined-outer-name
 @pytest.mark.parametrize("name", registry)  # type: ignore
 def test_optimizers_recommendation(name: str, recomkeeper: RecommendationKeeper) -> None:
+    if name in UNSEEDABLE:
+        raise SkipTest("Not playing nicely with the tests (unseedable)")
     if "BO" in name:
-        raise SkipTest("BO not cool these days for some reason!")
+        raise SkipTest("BO differs from one computer to another")
     # set up environment
     optimizer_cls = registry[name]
-    if name in UNSEEDABLE:
-        raise SkipTest("Not playing nicely with the tests (unseedable)")
     np.random.seed(None)
-    if optimizer_cls.recast or "SplitOptimizer" in name:
+    if optimizer_cls.recast:
         np.random.seed(12)
         random.seed(12)  # may depend on non numpy generator
     # budget=6 by default, larger for special cases needing more
@@ -207,9 +204,9 @@ def test_optimizers_recommendation(name: str, recomkeeper: RecommendationKeeper)
         # the following context manager speeds up BO tests
         # BEWARE: BO tests are deterministic but can get different results from a computer to another.
         # Reducing the precision could help in this regard.
-        patched = partial(acq_max, n_warmup=10000, n_iter=2)
-        with patch("bayes_opt.bayesian_optimization.acq_max", patched):
-            recom = optim.minimize(fitness)
+        # patched = partial(acq_max, n_warmup=10000, n_iter=2)
+        # with patch("bayes_opt.bayesian_optimization.acq_max", patched):
+        recom = optim.minimize(fitness)
     if name not in recomkeeper.recommendations.index:
         recomkeeper.recommendations.loc[name, :dimension] = tuple(recom.value)
         raise ValueError(f'Recorded the value for optimizer "{name}", please rerun this test locally.')
@@ -354,6 +351,15 @@ def test_bo_parametrization_and_parameters() -> None:
     opt.tell(new_candidate, 0.0)
 
 
+def test_bo_init() -> None:
+    arg = ng.p.Scalar(init=4, lower=1, upper=10).set_integer_casting()
+    gp_param = {'alpha': 1e-3, 'normalize_y': True,
+                'n_restarts_optimizer': 5, 'random_state': None}
+    my_opt = ng.optimizers.ParametrizedBO(gp_parameters=gp_param, initialization=None)
+    optimizer = my_opt(parametrization=arg, budget=10)
+    optimizer.minimize(np.abs)
+
+
 def test_chaining() -> None:
     budgets = [7, 19]
     optimizer = optlib.Chaining([optlib.LHSSearch, optlib.HaltonSearch, optlib.OnePlusOne], budgets)(2, 40)
@@ -448,6 +454,7 @@ def test_shiwa_dim1() -> None:
 )
 def test_shiwa_selection(name: str, param: tp.Any, budget: int, num_workers: int, expected: str, caplog: tp.Any) -> None:
     with caplog.at_level(logging.DEBUG, logger="nevergrad.optimization.optimizerlib"):
+        # pylint: disable=expression-not-assigned
         optlib.registry[name](param, budget=budget, num_workers=num_workers).optim  # type: ignore
         pattern = rf".*{name} selected (?P<name>\w+?) optimizer\."
         match = re.match(pattern, caplog.text.splitlines()[-1])