Vita
evolution_replacement.tcc
1/**
2 * \file
3 * \remark This file is part of VITA.
4 *
5 * \copyright Copyright (C) 2013-2024 EOS di Manlio Morini.
6 *
7 * \license
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
10 * You can obtain one at http://mozilla.org/MPL/2.0/
11 */
12
13#if !defined(VITA_EVOLUTION_REPLACEMENT_H)
14# error "Don't include this file directly, include the specific .h instead"
15#endif
16
17#if !defined(VITA_EVOLUTION_REPLACEMENT_TCC)
18#define VITA_EVOLUTION_REPLACEMENT_TCC
19
20///
21/// \param[in] pop current population
22/// \param[in] eva current evaluator
23///
24template<class T>
25strategy<T>::strategy(population<T> &pop, evaluator<T> &eva) : pop_(pop),
26 eva_(eva)
27{
28}
29
30///
31/// \param[in] parent coordinates of the parents (in the population).
32/// \param[in] offspring vector of the "children".
33/// \param[in,out] s statistical summary.
34///
35/// Parameters from the environment:
36/// * elitism is `true` => child replaces a member of the population only if
37/// child is better.
38///
39template<class T>
40void family_competition<T>::run(
41 const typename strategy<T>::parents_t &parent,
42 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
43{
44 auto &pop(this->pop_);
45 const auto elitism(pop.get_problem().env.elitism);
46 Expects(elitism != trilean::unknown);
47
48 const fitness_t fit_off(this->eva_(offspring[0]));
49
50 const fitness_t fit_parent[] =
51 {
52 this->eva_(pop[parent[0]]), this->eva_(pop[parent[1]])
53 };
54 const unsigned id_worst(fit_parent[0] < fit_parent[1] ? 0 : 1);
55
56 // The algorithm assumes that fitness values have same sign.
57 assert((fit_off[0] <= 0.0) == (fit_parent[0][0] <= 0.0));
58 assert((fit_off[0] <= 0.0) == (fit_parent[1][0] <= 0.0));
59
60 if (elitism == trilean::yes)
61 {
62 if (fit_off > fit_parent[id_worst])
63 pop[parent[id_worst]] = offspring[0];
64 }
65 else // !elitism
66 {
67 // THIS CODE IS APPROPRIATE ONLY WHEN FITNESS IS A SCALAR. It compiles when
68 // fitness is a vector but the replacement probability should be calculated
69 // in a better way.
70
71 //double replace(1.0 / (1.0 + exp(fit_parent[id_worst][0] - fit_off[0])));
72 double replace(1.0 - (fit_off[0]
73 / (fit_off[0] + fit_parent[id_worst][0])));
74 if (random::boolean(replace))
75 pop[parent[id_worst]] = offspring[0];
76 else
77 {
78 //replace = 1.0 / (1.0 + exp(f_parent[!id_worst][0] - fit_off[0]));
79 replace = 1.0 - (fit_off[0] / (fit_off[0] + fit_parent[!id_worst][0]));
80
81 if (random::boolean(replace))
82 pop[parent[!id_worst]] = offspring[0];
83 }
84 }
85
86 if (fit_off > s->best.score.fitness)
87 {
88 s->last_imp = s->gen;
89 s->best.solution = offspring[0];
90 s->best.score.fitness = fit_off;
91 }
92}
93
94///
95/// \param[in] parent coordinates of the candidate parents. Many
96/// selection algorithms sort the vector in descending
97/// fitness (with some exceptions, e.g.
98/// `selection::random`).
99/// Anyway here we assume that the last element
100/// contains the coordinates of the worst individual
101/// of the selection phase
102/// \param[in] offspring vector of the "children"
103/// \param[in,out] s statistical summary
104///
105/// Parameters from the environment:
106/// - elitism is `true` => child replaces a member of the population only if
107/// child is better.
108///
109template<class T>
110void tournament<T>::run(
111 const typename strategy<T>::parents_t &parent,
112 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
113{
114 auto &pop(this->pop_);
115 const auto elitism(pop.get_problem().env.elitism);
116 Expects(elitism != trilean::unknown);
117
118 const auto fit_off(this->eva_(offspring[0]));
119
120 // In old versions of Vita, the individual to be replaced was chosen with
121 // an ad-hoc kill tournament.
122 const auto rep_idx(parent.back());
123 const auto f_rep_idx(this->eva_(pop[rep_idx]));
124 const bool replace(f_rep_idx < fit_off);
125
126 if (elitism == trilean::no || replace)
127 pop[rep_idx] = offspring[0];
128
129 if (fit_off > s->best.score.fitness)
130 {
131 s->last_imp = s->gen;
132 s->best.solution = offspring[0];
133 s->best.score.fitness = fit_off;
134 }
135}
136
137///
138/// \param[in] l a layer
139/// \return the maximum allowed age for an individual in layer `l`
140///
141/// This is just a convenience method to save some keystroke.
142///
143template<class T>
144unsigned alps<T>::allowed_age(unsigned l) const
145{
146 const auto &pop(this->pop_);
147
148 return pop.get_problem().env.alps.allowed_age(l, pop.layers());
149}
150
151///
152/// \param[in] l a layer
153///
154/// Try to move individuals in layer `l` in the upper layer (calling
155/// try_add_to_layer for each individual).
156///
157template<class T>
158void alps<T>::try_move_up_layer(unsigned l)
159{
160 auto &pop(this->pop_);
161
162 if (l + 1 < pop.layers())
163 {
164 const auto n(pop.individuals(l));
165
166 for (auto i(decltype(n){0}); i < n; ++i)
167 try_add_to_layer(l + 1, pop[{l, i}]);
168 }
169}
170
171///
172/// \param[in] layer a layer
173/// \param[in] incoming an individual
174///
175/// We would like to add `incoming` in layer `layer`. The insertion will
176/// take place if:
177/// * `layer` is not full or...
178/// * after a "kill tournament" selection, the worst individual found is
179/// too old for `layer` while the incoming one is within the limits or...
180/// * the worst individual has a lower fitness than the incoming one and
181/// both are simultaneously within/outside the time frame of `layer`.
182///
183template<class T>
184bool alps<T>::try_add_to_layer(unsigned layer, const T &incoming)
185{
186 using coord = typename population<T>::coord;
187
188 auto &p(this->pop_);
189 assert(layer < p.layers());
190
191 if (p.individuals(layer) < p.allowed(layer))
192 {
193 p.add_to_layer(layer, incoming); // layer not full... inserting incoming
194 return true;
195 }
196
197 // Layer is full, can we replace an existing individual?
198 const auto m_age(allowed_age(layer));
199
200 // Well, let's see if the worst individual we can find with a tournament...
201 coord c_worst{layer, random::sup(p.individuals(layer))};
202 auto f_worst(this->eva_(p[c_worst]));
203
204 auto rounds(p.get_problem().env.tournament_size);
205 while (rounds--)
206 {
207 const coord c_x{layer, random::sup(p.individuals(layer))};
208 const auto f_x(this->eva_(p[c_x]));
209
210 if ((p[c_x].age() > p[c_worst].age() && p[c_x].age() > m_age) ||
211 (p[c_worst].age() <= m_age && p[c_x].age() <= m_age &&
212 f_x < f_worst))
213 {
214 c_worst = c_x;
215 f_worst = f_x;
216 }
217 }
218
219 // ... is worse than the incoming individual.
220 if ((incoming.age() <= m_age && p[c_worst].age() > m_age) ||
221 ((incoming.age() <= m_age || p[c_worst].age() > m_age) &&
222 this->eva_(incoming) >= f_worst))
223 {
224 if (layer + 1 < p.layers())
225 try_add_to_layer(layer + 1, p[c_worst]);
226 p[c_worst] = incoming;
227
228 return true;
229 }
230
231 return false;
232}
233
234///
235/// \param[in] parent coordinates of the candidate parents.
236/// The list is sorted in descending fitness, so the
237/// last element is the coordinates of the worst individual
238/// of the tournament.
239/// \param[in] offspring vector of the "children".
240/// \param[in,out] s statistical summary.
241///
242/// Parameters from the environment:
243/// * elitism is `true` => a new best individual is always inserted into the
244/// population.
245///
246template<class T>
247void alps<T>::run(
248 const typename strategy<T>::parents_t &parent,
249 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
250{
251 const auto layer(std::max(parent[0].layer, parent[1].layer));
252 const auto f_off(this->eva_(offspring[0]));
253 const auto &pop(this->pop_);
254 const auto elitism(pop.get_problem().env.elitism);
255
256 Expects(elitism != trilean::unknown);
257
258 bool ins;
259#if defined(MUTUAL_IMPROVEMENT)
260 // To protect the algorithm from the potential deleterious effect of intense
261 // exploratory dynamics, we can use a constraint which mandate that an
262 // individual must be better than both its parents before insertion into
263 // the population.
264 // See "Exploiting The Path of Least Resistance In Evolution" (Gearoid Murphy
265 // and Conor Ryan).
266 if (f_off > this->eva_(pop[parent[0]]) && f_off > this->eva_(pop[parent[1]]))
267#endif
268 {
269 ins = try_add_to_layer(layer, offspring[0]);
270 }
271
272 if (f_off > s->best.score.fitness)
273 {
274 // Sometimes a new best individual is discovered in a lower layer but he is
275 // too old for its layer and the random tournament may choose only "not
276 // aged" individuals (i.e. individuals within the age limit of their layer).
277 // When this happen the new best individual would be lost without the
278 // command below.
279 // There isn't an age limit for the last layer so try_add_to_layer will
280 // always succeed.
281 if (!ins && elitism == trilean::yes)
282 try_add_to_layer(pop.layers() - 1, offspring[0]);
283
284 s->last_imp = s->gen;
285 s->best.solution = offspring[0];
286 s->best.score.fitness = f_off;
287 }
288}
289
290///
291/// \param[in] parent coordinates of the candidate parents.
292/// The list is sorted in descending pareto-layer
293/// dominance (from pareto non dominated front to
294/// dominated points.
295/// \param[in] offspring vector of the "children".
296/// \param[in,out] s statistical summary.
297///
298/// To determine whether a new individual x is to be accepted into the main
299/// population, we compare it with the `parent` buffer, simply ensuring
300/// that the new individual is not dominated.
301///
302/// If this is the case, then it is immediately accepted and is inserted
303/// according to the replacement rules. The only parameter that needs to be
304/// determined in advance is the tournament size, a parameter that would
305/// exist in a single objective optimisation anyway.
306///
307/// Parameters from the environment:
308/// * elitism is `true` => child replaces a member of the population only if
309/// child is better.
310///
311/// \see
312/// "A Robust Evolutionary Technique for Coupled and Multidisciplinary Design
313/// Optimization problems in Aeronautics" - L.F.Gonzalez, E.J. Whithney,
314/// K. Srinivas, S. Armfield, J. Periaux.
315///
316template<class T>
317void pareto<T>::run(
318 const typename strategy<T>::parents_t &parent,
319 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
320{
321 auto &pop(this->pop_);
322 const auto elitism(pop.get_problem().env.elitism);
323
324 Expects(elitism != trilean::unknown);
325
326 const auto fit_off(this->eva_(offspring[0]));
327/*
328 for (auto i(parent.rbegin()); i != parent.rend(); ++i)
329 {
330 const auto fit_i(this->eva_(pop[*i]));
331
332 if (fit_off.dominating(fit_i))
333 {
334 pop[i] = offspring[0];
335
336 if (fit_off > s->best.score.fitness)
337 {
338 s->last_imp = s->gen;
339 s->best.solution = offspring[0];
340 s->best.score.fitness = fit_off;
341 }
342
343 break;
344 }
345 }
346*/
347
348 bool dominated(false);
349 for (const auto &i : parent)
350 {
351 const auto fit_i(this->eva_(pop[i]));
352
353 if (fit_i.dominating(fit_off))
354 {
355 dominated = true;
356 break;
357 }
358 }
359
360 if (elitism == trilean::no || !dominated)
361 pop[parent.back()] = offspring[0];
362
363 if (fit_off > s->best.score.fitness)
364 {
365 s->last_imp = s->gen;
366 s->best.solution = offspring[0];
367 s->best.score.fitness = fit_off;
368 }
369}
370#endif // Include guard