dignea  1.0.0
Diverse Instance Generator with Novelty Search and Evolutionary Algorithms
MapElites.h
Go to the documentation of this file.
1 
12 #ifndef DIGNEA_MAP_ELITES_H
13 #define DIGNEA_MAP_ELITES_H
14 
15 #include <dignea/generator/EIG.h>
16 
17 #include <algorithm>
18 #include <array>
19 #include <cstdint>
20 #include <iterator>
21 #include <map>
22 #include <numeric>
23 #include <ranges>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27 
28 #include "NumCpp.hpp"
29 
30 using namespace std;
38 using FeatureInfo = std::tuple<float, float, int>;
39 
46 using Features = std::map<int, FeatureInfo>;
47 
48 using Key = std::array<float, 8>;
49 
50 using Bins = nc::NdArray<float>;
51 
65 template <typename IP, typename IS, typename OP, typename OS>
66 class MapElites : public EIG<IP, IS, OP, OS> {
67  public:
68  using Alg = AbstractEA<OS>;
69  using Value = IS;
70  using Grid = std::map<Key, Value>;
71 
72  MapElites();
73 
74  virtual ~MapElites() = default;
75 
76  void run() override;
77 
78  [[nodiscard]] json to_json() const override;
79 
82  [[nodiscard]] string getName() const override { return "MapElites"; };
83 
84  virtual Front<IS> getResults() const override;
85 
86  void setFeaturesInfo(const Features &f);
87 
88  protected:
89  void createInitialPopulation() override;
90 
91  void evaluatePopulation(vector<IS> &individuals) override;
92 
93  private:
94  void mapToGrid(vector<IS> &individuals);
95 
96  Key constructKey(const vector<float> &features);
97 
98  vector<IS> gridToVector();
99 
100  private:
101  Features features; // Feature information for the domain
102  Grid grid; // Grid where the best instances are represented
103  array<Bins, 8> bins;
104 };
105 
113 template <typename IP, typename IS, typename OP, typename OS>
115  : EIG<IP, IS, OP, OS>(), features(), grid(), bins() {
116  this->weightedFitness = nullptr;
117  this->noveltySearch = nullptr;
118 }
119 
129 template <typename IP, typename IS, typename OP, typename OS>
131  vector<IS> v;
132  std::transform(this->grid.begin(), this->grid.end(), std::back_inserter(v),
133  [](auto &entry) { return entry.second; });
134  return v;
135 }
136 
145 template <typename IP, typename IS, typename OP, typename OS>
147  this->population.reserve(this->populationSize);
148  this->population = this->instProb->createSolutions(this->populationSize);
149  this->evaluatePopulation(this->population);
150  this->mapToGrid(this->population);
151  this->updateEvolution(this->population);
152  this->initProgress();
153 }
154 
163 template <typename IP, typename IS, typename OP, typename OS>
165  if (this->features.empty()) {
166  throw std::logic_error("Features not set in MapElites::run");
167  }
168  if (this->instProb) {
169 #ifdef DEBUG
170  std::cout << "Running MapElites" << std::endl;
171 #endif
172  this->createInitialPopulation();
173  this->startTime = chrono::system_clock::now();
174  while (!this->isStoppingConditionReached()) {
175 #ifdef DEBUG
176  std::cout << "Performing Generation " << this->performedEvaluations
177  << std::endl;
178 #endif
179  vector<IS> offspring(this->populationSize);
180  vector pop = this->gridToVector();
181  if (pop.empty()) {
182  pop = this->population;
183  }
184 
185  for (int i = 0; i < this->populationSize; i++) {
186  IS child = this->selection->select(pop);
187  // this->reproduction(firstChild, secondChild);
188  // Vanilla MapElites only perform mutation
189  this->mutation->run(child, this->mutationRate,
190  this->instProb.get());
191  offspring[i] = child;
192  }
193 
194  this->evaluatePopulation(offspring);
195  this->mapToGrid(offspring);
196 
197  // Updates the fitness evolution
198  this->updateEvolution(this->population);
199  this->updateProgress();
200  }
201  this->endTime = chrono::system_clock::now();
202  this->finishProgress();
203 
204  } else {
205  throw std::logic_error("Problem not set in MapElites::run");
206  }
207  // Creates the population so we avoid the `this' argument discards
208  // qualifiers in getResults method
209  this->population.clear();
210  this->population = gridToVector();
211 }
212 
223 template <typename IP, typename IS, typename OP, typename OS>
225  this->features = f;
226  // Calculate the bins
227  for (int i = 0; i < 8; i++) {
228  auto [start, stop, num] = this->features[i];
229  bins[i] = nc::linspace(start, stop, num);
230  }
231 }
232 
244 template <typename IP, typename IS, typename OP, typename OS>
245 Key MapElites<IP, IS, OP, OS>::constructKey(const vector<float> &descriptor) {
246  Key key;
247  // Traducimos a los bins
248  for (unsigned int i = 0; i < descriptor.size(); i++) {
249  // Creates a vector with a single value
250  const nc::NdArray<float> x = {descriptor[i]};
251  const nc::NdArray<float> bin = this->bins[i];
252 
253  const auto uniqueBins = unique(bin);
254  auto result = nc::NdArray<float>(1, x.size());
255  result.fill(0);
256 
257  typename decltype(result)::size_type idx{0};
258  std::for_each(x.begin(), x.end(),
259  [&uniqueBins, &result, &idx](const auto value) {
260  const auto upperBin = std::upper_bound(
261  uniqueBins.begin(), uniqueBins.end(), value);
262  result[idx++] = static_cast<float>(
263  std::distance(uniqueBins.begin(), upperBin));
264  });
265 
266  key[i] = result[0];
267  }
268  return key;
269 }
270 
281 template <typename IP, typename IS, typename OP, typename OS>
282 void MapElites<IP, IS, OP, OS>::mapToGrid(vector<IS> &individuals) {
283  // Para cada individuo, obtenemos su descriptor
284  for (IS &ind : individuals) {
285  // Only consider individuals with positive fitness
286  if (ind.getFitness() > 0) {
287  vector descriptor = ind.getFeatures();
288  Key key = this->constructKey(descriptor);
289  // Tries to insert the individual in the grid
290  // If there is not any solution in the bin we include ind
291  if (!this->grid.contains(key)) {
292  this->grid.insert({key, ind});
293  // If there's a solution but ind has better fitness, update
294  } else if (ind.getFitness() > this->grid[key].getFitness()) {
295  this->grid[key] = ind;
296  }
297  }
298  }
299 }
300 
311 template <typename IP, typename IS, typename OP, typename OS>
312 void MapElites<IP, IS, OP, OS>::evaluatePopulation(vector<IS> &individuals) {
313  this->instProb->beforeEvaluation(individuals);
314  for (IS &individual : individuals) {
315  // Creates the optimisation problem for the EAs
316  shared_ptr<OP> optProblem = this->instProb->genOptProblem(individual);
317 
318  vector<float> avgBestFitEAs; // Avg. Best fitness from each alg.
319  avgBestFitEAs.reserve(this->algPortfolio.size());
320  vector<vector<float>> bestSolsEAs; // Best #reps solutions/algorithm
321 
322  for (unique_ptr<Alg> &config : this->algPortfolio) {
323  vector<float> bestF(this->repetitions, 0.0); // Resets the bestF
324  // Run the problem rep times
325  for (int rep = 0; rep < this->repetitions; rep++) {
326  config->setProblem(optProblem);
327  config->run();
328  // Get the results and compute the best founds
329  Front<OS> front = config->getResults();
330  OS bestSolution = this->bestFitness.getBestSolution(front);
331  bestF[rep] = bestSolution.getFitness();
332  }
333  // Saves avg. of the best found in #reps
334  float averageBestFitness = this->averageFitness.computeValue(bestF);
335  avgBestFitEAs.push_back(averageBestFitness);
336  // Saves all best #reps solutions for analysis
337  bestSolsEAs.push_back(bestF);
338  }
339 
340  // Saves the fitness founds to future analysis
341  individual.setConfigFitness(bestSolsEAs);
342  individual.setAvgConfigFitness(avgBestFitEAs);
343  // Set the biased fitness depending on the type of instance to generate
344  // (easy or hard)
345  auto f = this->instanceFitness->compute(avgBestFitEAs);
346  individual.setBiasedFitness(f);
347  individual.setFitness(f);
348  }
349  // Problem dependent changes that must be executed after evaluate the
350  // instances
351  this->instProb->afterEvaluation(individuals);
352 }
353 
363 template <typename IP, typename IS, typename OP, typename OS>
365  return Front<IS>(this->population);
366 }
367 
376 template <typename IP, typename IS, typename OP, typename OS>
378  json data;
379  Evolution copy = this->evolution;
380  AvgEvolution avgCopy = this->avgEvolution;
381  data["name"] = this->getName();
382  data["max_generations"] = this->getMaxEvaluations();
383  data["repetitions"] = this->repetitions;
384  data["pop_size"] = this->populationSize;
385  data["mutation"] = this->mutation->getName();
386  data["crossover"] = this->crossover->getName();
387  data["mutation_rate"] = this->mutationRate;
388  data["crossover_rate"] = this->crossoverRate;
389  data["elapsed_time"] = this->elapsedTime;
390  data["evolution"] = copy.results();
391  data["avg_evolution"] = avgCopy.results();
392  int i = 0;
393  bool target = true;
394  for (const unique_ptr<Alg> &config : this->algPortfolio) {
395  data["portfolio"][i] = config->to_json();
396  data["portfolio"][i]["isTarget"] = target;
397  i++;
398  target = false;
399  }
400  data["features_info"] = this->features;
401  return data;
402 }
403 
404 #endif
std::tuple< float, float, int > FeatureInfo
FeatureInfo defines the information for a specific feature.
Definition: MapElites.h:38
std::map< int, FeatureInfo > Features
Features defines the information a feature.
Definition: MapElites.h:46
Class to define an Abstract Evolutionary Algorithm. This is the base skeleton for future extensions.
Definition: AbstractEA.h:42
Instance Generation Algorithm. Known as Meta-Evolutionary Algorithm (EIG). This algorithm uses a Weig...
Definition: EIG.h:55
unique_ptr< NoveltySearch< IS > > noveltySearch
Definition: EIG.h:255
Front class which stores the final results of an EA execution.
Definition: Front.h:26
Instance Generation Algorithm. Known as Meta-Evolutionary Algorithm (MapElites). This algorithm uses ...
Definition: MapElites.h:66
void evaluatePopulation(vector< IS > &individuals) override
Evaluates the population of individuals with all the algorithms in the portfolio.
Definition: MapElites.h:312
void createInitialPopulation() override
Creates a initial population of Problem Instances of the OP type. Uses the IP method "createSolutions...
Definition: MapElites.h:146
void run() override
Interface to run the MapElites for generating a set of instances for the optimisation problem defined...
Definition: MapElites.h:164
virtual Front< IS > getResults() const override
Returns a set of individuals as a result of the evolutionary method. The individual are instances for...
Definition: MapElites.h:364
string getName() const override
Get the name of the algorithm.
Definition: MapElites.h:82
json to_json() const override
Creates the JSON representation of the MapElites.
Definition: MapElites.h:377
MapElites()
Creates a default MapElites with only the evaluation as EasyInstances.
Definition: MapElites.h:114
void setFeaturesInfo(const Features &f)
Creates the pre-defined bins for each feature in the domain Creates the linspace with nBins different...
Definition: MapElites.h:224