3 * \remark This file is part of VITA.
5 * \copyright Copyright (C) 2013-2024 EOS di Manlio Morini.
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/
13#if !defined(VITA_EVOLUTION_REPLACEMENT_H)
14# error "Don't include this file directly, include the specific .h instead"
17#if !defined(VITA_EVOLUTION_REPLACEMENT_TCC)
18#define VITA_EVOLUTION_REPLACEMENT_TCC
21/// \param[in] pop current population
22/// \param[in] eva current evaluator
25strategy<T>::strategy(population<T> &pop, evaluator<T> &eva) : pop_(pop),
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.
35/// Parameters from the environment:
36/// * elitism is `true` => child replaces a member of the population only if
40void family_competition<T>::run(
41 const typename strategy<T>::parents_t &parent,
42 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
44 auto &pop(this->pop_);
45 const auto elitism(pop.get_problem().env.elitism);
46 Expects(elitism != trilean::unknown);
48 const fitness_t fit_off(this->eva_(offspring[0]));
50 const fitness_t fit_parent[] =
52 this->eva_(pop[parent[0]]), this->eva_(pop[parent[1]])
54 const unsigned id_worst(fit_parent[0] < fit_parent[1] ? 0 : 1);
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));
60 if (elitism == trilean::yes)
62 if (fit_off > fit_parent[id_worst])
63 pop[parent[id_worst]] = offspring[0];
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
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];
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]));
81 if (random::boolean(replace))
82 pop[parent[!id_worst]] = offspring[0];
86 if (fit_off > s->best.score.fitness)
89 s->best.solution = offspring[0];
90 s->best.score.fitness = fit_off;
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
105/// Parameters from the environment:
106/// - elitism is `true` => child replaces a member of the population only if
110void tournament<T>::run(
111 const typename strategy<T>::parents_t &parent,
112 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
114 auto &pop(this->pop_);
115 const auto elitism(pop.get_problem().env.elitism);
116 Expects(elitism != trilean::unknown);
118 const auto fit_off(this->eva_(offspring[0]));
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);
126 if (elitism == trilean::no || replace)
127 pop[rep_idx] = offspring[0];
129 if (fit_off > s->best.score.fitness)
131 s->last_imp = s->gen;
132 s->best.solution = offspring[0];
133 s->best.score.fitness = fit_off;
138/// \param[in] l a layer
139/// \return the maximum allowed age for an individual in layer `l`
141/// This is just a convenience method to save some keystroke.
144unsigned alps<T>::allowed_age(unsigned l) const
146 const auto &pop(this->pop_);
148 return pop.get_problem().env.alps.allowed_age(l, pop.layers());
152/// \param[in] l a layer
154/// Try to move individuals in layer `l` in the upper layer (calling
155/// try_add_to_layer for each individual).
158void alps<T>::try_move_up_layer(unsigned l)
160 auto &pop(this->pop_);
162 if (l + 1 < pop.layers())
164 const auto n(pop.individuals(l));
166 for (auto i(decltype(n){0}); i < n; ++i)
167 try_add_to_layer(l + 1, pop[{l, i}]);
172/// \param[in] layer a layer
173/// \param[in] incoming an individual
175/// We would like to add `incoming` in layer `layer`. The insertion will
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`.
184bool alps<T>::try_add_to_layer(unsigned layer, const T &incoming)
186 using coord = typename population<T>::coord;
189 assert(layer < p.layers());
191 if (p.individuals(layer) < p.allowed(layer))
193 p.add_to_layer(layer, incoming); // layer not full... inserting incoming
197 // Layer is full, can we replace an existing individual?
198 const auto m_age(allowed_age(layer));
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]));
204 auto rounds(p.get_problem().env.tournament_size);
207 const coord c_x{layer, random::sup(p.individuals(layer))};
208 const auto f_x(this->eva_(p[c_x]));
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 &&
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))
224 if (layer + 1 < p.layers())
225 try_add_to_layer(layer + 1, p[c_worst]);
226 p[c_worst] = incoming;
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.
242/// Parameters from the environment:
243/// * elitism is `true` => a new best individual is always inserted into the
248 const typename strategy<T>::parents_t &parent,
249 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
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);
256 Expects(elitism != trilean::unknown);
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
264 // See "Exploiting The Path of Least Resistance In Evolution" (Gearoid Murphy
266 if (f_off > this->eva_(pop[parent[0]]) && f_off > this->eva_(pop[parent[1]]))
269 ins = try_add_to_layer(layer, offspring[0]);
272 if (f_off > s->best.score.fitness)
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
279 // There isn't an age limit for the last layer so try_add_to_layer will
281 if (!ins && elitism == trilean::yes)
282 try_add_to_layer(pop.layers() - 1, offspring[0]);
284 s->last_imp = s->gen;
285 s->best.solution = offspring[0];
286 s->best.score.fitness = f_off;
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
295/// \param[in] offspring vector of the "children".
296/// \param[in,out] s statistical summary.
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.
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.
307/// Parameters from the environment:
308/// * elitism is `true` => child replaces a member of the population only if
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.
318 const typename strategy<T>::parents_t &parent,
319 const typename strategy<T>::offspring_t &offspring, summary<T> *s)
321 auto &pop(this->pop_);
322 const auto elitism(pop.get_problem().env.elitism);
324 Expects(elitism != trilean::unknown);
326 const auto fit_off(this->eva_(offspring[0]));
328 for (auto i(parent.rbegin()); i != parent.rend(); ++i)
330 const auto fit_i(this->eva_(pop[*i]));
332 if (fit_off.dominating(fit_i))
334 pop[i] = offspring[0];
336 if (fit_off > s->best.score.fitness)
338 s->last_imp = s->gen;
339 s->best.solution = offspring[0];
340 s->best.score.fitness = fit_off;
348 bool dominated(false);
349 for (const auto &i : parent)
351 const auto fit_i(this->eva_(pop[i]));
353 if (fit_i.dominating(fit_off))
360 if (elitism == trilean::no || !dominated)
361 pop[parent.back()] = offspring[0];
363 if (fit_off > s->best.score.fitness)
365 s->last_imp = s->gen;
366 s->best.solution = offspring[0];
367 s->best.score.fitness = fit_off;
370#endif // Include guard