Repository: facebookresearch/nevergrad

Issue:
Make `Candidate` and `Individual` API converge.
Convergence would be helpful for simplicity's sake and to have more generic population based optimizer code.



Discussion:
Only PSO still uses this, it will soon disappear
PSO to disappear with the Population class, but Individual still remains for TBPSA

Resolving patch:
diff --git a/nevergrad/optimization/optimizerlib.py b/nevergrad/optimization/optimizerlib.py
--- a/nevergrad/optimization/optimizerlib.py
+++ b/nevergrad/optimization/optimizerlib.py
@@ -15,7 +15,6 @@
 from nevergrad.parametrization import discretization
 from nevergrad.parametrization import helpers as paramhelpers
 from nevergrad.common.typetools import ArrayLike
-from . import utils
 from . import base
 from . import mutations
 from .base import registry as registry
@@ -489,7 +488,7 @@ def __init__(self, parametrization: IntOrParameter, budget: Optional[int] = None
         self.current_center: np.ndarray = np.zeros(self.dimension)
         self._loss_record: List[float] = []
         # population
-        self._evaluated_population: List[base.utils.Individual] = []
+        self.population: List[p.Parameter] = []
 
     def _internal_provide_recommendation(self) -> ArrayLike:  # This is NOT the naive version. We deal with noise.
         return self.current_center
@@ -502,6 +501,7 @@ def _internal_ask_candidate(self) -> p.Parameter:
         return candidate
 
     def _internal_tell_candidate(self, candidate: p.Parameter, value: float) -> None:
+        candidate._meta["loss"] = value
         self._loss_record += [value]
         if len(self._loss_record) >= 5 * self.llambda:
             first_fifth = self._loss_record[: self.llambda]
@@ -520,19 +520,15 @@ def _internal_tell_candidate(self, candidate: p.Parameter, value: float) -> None
                 self.llambda = max(self.llambda, self.num_workers)
                 self.mu = self.llambda // 4
             self._loss_record = []
-        data = candidate.get_standardized_data(reference=self.parametrization)
-        particle = base.utils.Individual(data)
-        particle._parameters = np.array([candidate._meta["sigma"]])
-        particle.value = value
-        self._evaluated_population.append(particle)
-        if len(self._evaluated_population) >= self.llambda:
+        self.population.append(candidate)
+        if len(self.population) >= self.llambda:
             # Sorting the population.
-            self._evaluated_population.sort(key=lambda p: p.value)
+            self.population.sort(key=lambda c: c._meta["loss"])
             # Computing the new parent.
-            self.current_center = sum(p.x for p in self._evaluated_population[: self.mu]) / self.mu  # type: ignore
-            self.sigma = np.exp(np.sum(np.log([p._parameters[0]
-                                               for p in self._evaluated_population[: self.mu]])) / self.mu)
-            self._evaluated_population = []
+            self.current_center = sum(c.get_standardized_data(reference=self.parametrization)  # type: ignore
+                                      for c in self.population[: self.mu]) / self.mu
+            self.sigma = np.exp(np.sum(np.log([c._meta["sigma"] for c in self.population[: self.mu]])) / self.mu)
+            self.population = []
 
     def _internal_tell_not_asked(self, candidate: p.Parameter, value: float) -> None:
         data = candidate.get_standardized_data(reference=self.parametrization)
@@ -1596,12 +1592,13 @@ def __init__(self, parametrization: IntOrParameter, budget: Optional[int] = None
         self.current_center: np.ndarray = np.zeros(self.dimension)
         self._loss_record: List[float] = []
         # population
-        self._evaluated_population: List[base.utils.Individual] = []
+        self.population: List[p.Parameter] = []
 
     def _internal_provide_recommendation(self) -> ArrayLike:
         return self.current_bests["optimistic"].x  # Naive version for now
 
     def _internal_tell_candidate(self, candidate: p.Parameter, value: float) -> None:
+        candidate._meta["loss"] = value
         self._loss_record += [value]
         if len(self._loss_record) >= 5 * self.llambda:
             first_fifth = self._loss_record[: self.llambda]
@@ -1620,23 +1617,20 @@ def _internal_tell_candidate(self, candidate: p.Parameter, value: float) -> None
                 self.llambda = max(self.llambda, self.num_workers)
                 self.mu = self.llambda // 4
             self._loss_record = []
-        data = candidate.get_standardized_data(reference=self.parametrization)
-        particle = base.utils.Individual(data)
-        particle._parameters = np.array([candidate._meta["sigma"]])
-        particle.value = value
-        self._evaluated_population.append(particle)
-        if len(self._evaluated_population) >= self.llambda:
+        self.population.append(candidate)
+        if len(self.population) >= self.llambda:
             # Sorting the population.
-            self._evaluated_population.sort(key=lambda p: p.value)
+            self.population.sort(key=lambda c: c._meta["loss"])
             # Computing the new parent.
-            self.current_center = sum(p.x for p in self._evaluated_population[: self.mu]) / self.mu  # type: ignore
+            self.current_center = sum(c.get_standardized_data(reference=self.parametrization)  # type: ignore
+                                      for c in self.population[: self.mu]) / self.mu
+            t1 = [(c.get_standardized_data(reference=self.parametrization) - self.current_center)**2 for c in self.population[: self.mu]]
             # EMNA update
-            t1 = [(self._evaluated_population[i].x - self.current_center)**2 for i in range(self.mu)]
             self.sigma = np.sqrt(sum(t1) / (self.mu))
             imp = max(1, (np.log(self.llambda) / 2)**(1 / self.dimension))
             if self.num_workers / self.dimension > 16:
                 self.sigma /= imp
-            self._evaluated_population = []
+            self.population = []
 
 
 @registry.register
diff --git a/nevergrad/optimization/utils.py b/nevergrad/optimization/utils.py
--- a/nevergrad/optimization/utils.py
+++ b/nevergrad/optimization/utils.py
@@ -6,7 +6,6 @@
 import warnings
 import operator
 import typing as tp
-from uuid import uuid4
 import numpy as np
 from nevergrad.common.tools import OrderedSet
 from nevergrad.common.typetools import ArrayLike
@@ -297,19 +296,6 @@ def sensible_default(cls, num_workers: int, dimension: int) -> 'Pruning':
         return cls(min_len, max(max_len, max_len_1gb))
 
 
-class Individual:  # TODO remove
-
-    def __init__(self, x: ArrayLike) -> None:
-        self.x = np.array(x, copy=False)
-        self.uid = uuid4().hex
-        self.value: tp.Optional[float] = None
-        self._parameters = np.array([])
-        self._active = True
-
-    def __repr__(self) -> str:
-        return f"Indiv<{self.x}, {self.value}>"
-
-
 class UidQueue:
     """Queue of uids to handle a population. This keeps track of:
     - told uids


Test patch: