3 * \remark This file is part of VITA.
5 * \copyright Copyright (C) 2013-2023 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_H)
14# error "Don't include this file directly, include the specific .h instead"
17#if !defined(VITA_EVOLUTION_TCC)
18#define VITA_EVOLUTION_TCC
23/// \return `true` when the user presses the '.' key
25inline bool user_stop()
27 const bool stop(keypressed('.'));
31 vitaINFO << "Stopping evolution...";
38/// Resets the term and restores the default signal handlers.
42 std::signal(SIGABRT, SIG_DFL);
43 std::signal(SIGINT, SIG_DFL);
44 std::signal(SIGTERM, SIG_DFL);
50/// If the program receives a SIGABRT / SIGINT / SIGTERM, it must handle
51/// the signal and reset the terminal to the initial state.
53inline void signal_handler(int signum)
61/// Sets the term in raw mode and handles the interrupt signals.
65 // Install our signal handler.
66 std::signal(SIGABRT, term::signal_handler);
67 std::signal(SIGINT, term::signal_handler);
68 std::signal(SIGTERM, term::signal_handler);
75/// \param[in] p the current problem
76/// \param[in] eva evaluator used during the evolution
78template<class T, template<class> class ES>
79evolution<T, ES>::evolution(const problem &p, evaluator<T> &eva)
80 : pop_(p), eva_(eva), es_(pop_, eva_, &stats_), after_generation_callback_()
86/// Sets a callback function called at the end of every generation.
88/// \param[in] f callback function
89/// \return a reference to `*this` object (fluent interface)
91template<class T, template<class> class ES>
92evolution<T, ES> &evolution<T, ES>::after_generation(
93 after_generation_callback_t f)
95 after_generation_callback_ = f;
100/// \param[in] s an up to date evolution summary
101/// \return `true` when evolution should be interrupted
103template<class T, template<class> class ES>
104bool evolution<T, ES>::stop_condition(const summary<T> &s) const
106 const auto generations(pop_.get_problem().env.generations);
107 Expects(generations);
109 // Check the number of generations.
110 if (s.gen > generations)
113 if (term::user_stop())
116 // Check strategy specific stop conditions.
117 return es_.stop_condition();
121/// \return statistical information about the population
123template<class T, template<class> class ES>
124analyzer<T> evolution<T, ES>::get_stats() const
128 for (auto it(pop_.begin()), end(pop_.end()); it != end; ++it)
129 az.add(*it, eva_(*it), it.layer());
135/// Saves working / statistical informations in a log file.
137/// \param[in] run_count run number
139/// Data are written in a CSV-like fashion and are partitioned in blocks
140/// separated by two blank lines:
147/// where each block is a set of line like this:
149/// data_1 [space] data_2 [space] ... [space] data_n
151/// We use this format, instead of XML, because statistics are produced
152/// incrementally and so it's simple and fast to append new data to a
153/// CSV-like file. Note also that it's simple to extract and plot data with
156template<class T, template<class> class ES>
157void evolution<T, ES>::log_evolution(unsigned run_count) const
159 static unsigned last_run(0);
161 const auto &env(pop_.get_problem().env);
163 const auto fullpath([env](const std::filesystem::path &f)
165 return env.stat.dir / f;
168 if (!env.stat.dynamic_file.empty())
170 std::ofstream f_dyn(fullpath(env.stat.dynamic_file), std::ios_base::app);
173 if (last_run != run_count)
176 f_dyn << run_count << ' ' << stats_.gen;
178 if (stats_.best.solution.empty())
181 f_dyn << ' ' << stats_.best.score.fitness[0];
183 f_dyn << ' ' << stats_.az.fit_dist().mean()[0]
184 << ' ' << stats_.az.fit_dist().standard_deviation()[0]
185 << ' ' << stats_.az.fit_dist().entropy()
186 << ' ' << stats_.az.fit_dist().min()[0]
187 << ' ' << static_cast<unsigned>(stats_.az.length_dist().mean())
188 << ' ' << stats_.az.length_dist().standard_deviation()
189 << ' ' << static_cast<unsigned>(stats_.az.length_dist().max())
190 << ' ' << stats_.mutations
191 << ' ' << stats_.crossovers
192 << ' ' << stats_.az.functions(0)
193 << ' ' << stats_.az.terminals(0)
194 << ' ' << stats_.az.functions(1)
195 << ' ' << stats_.az.terminals(1);
197 for (unsigned active(0); active <= 1; ++active)
198 for (const auto &symb_stat : stats_.az)
199 f_dyn << ' ' << symb_stat.first->name()
200 << ' ' << symb_stat.second.counter[active];
203 if (!stats_.best.solution.empty())
204 f_dyn << out::in_line << stats_.best.solution;
209 if (!env.stat.population_file.empty())
211 std::ofstream f_pop(fullpath(env.stat.population_file),
215 if (last_run != run_count)
218 for (const auto &f : stats_.az.fit_dist().seen())
219 // f.first: value, f.second: frequency
220 f_pop << run_count << ' ' << stats_.gen << ' '
221 << std::fixed << std::scientific
222 << std::setprecision(
223 std::numeric_limits<fitness_t::value_type>::digits10 + 2)
224 << f.first[0] << ' ' << f.second << '\n';
228 es_.log_strategy(last_run, run_count);
230 if (last_run != run_count)
231 last_run = run_count;
235/// Prints evolution information (if `log::reporting_level >= log::lOUTPUT`).
237/// \param[in] k current generation
238/// \param[in] run_count total number of runs planned
239/// \param[in] summary if `true` prints a summary line
240/// \param[in] from_last_msg time elapsed from the last message
242template<class T, template<class> class ES>
243void evolution<T, ES>::print_progress(unsigned k, unsigned run_count,
244 bool summary, timer *from_last_msg) const
246 if (log::lOUTPUT >= log::reporting_level)
248 const unsigned perc(100 * k / pop_.individuals());
250 std::cout << "Run " << run_count << '.' << std::setw(6)
251 << stats_.gen << " (" << std::setw(3)
252 << perc << "%): fitness " << stats_.best.score.fitness
255 std::cout << "Crunching " << run_count << '.' << stats_.gen << " ("
256 << std::setw(3) << perc << "%)\r";
258 std::cout << std::flush;
260 from_last_msg->restart();
265/// The evolutionary core loop.
267/// \param[in] run_count run number (used for printing and logging)
268/// \param[in] shake the "shake data" function. It's used to alter the
269/// training set so that evolution would take place in a
270/// dynamic environment
271/// \return a partial summary of the search (see notes)
273/// The genetic programming loop:
275/// * select the individual(s) to participate (default algorithm: tournament
276/// selection) in the genetic operation;
277/// * perform genetic operation creating a new offspring individual;
278/// * place the offspring into the original population (steady state)
279/// replacing a bad individual.
281/// This whole process repeats until the termination criteria is satisfied.
282/// With any luck, it will produce an individual that solves the problem at
286/// The return value is a partial summary: the `measurement` section is only
287/// partially filled (fitness) since many metrics are expensive to calculate
288/// and not significative for all kind of problems (e.g. f1-score for a
289/// symbolic regression problem). The src_search class has a simple scheme to
290/// request the computation of additional metrics.
292template<class T, template<class> class ES>
294const summary<T> &evolution<T, ES>::run(unsigned run_count, S shake)
297 stats_.best.solution = pop_[{0, 0}];
298 stats_.best.score.fitness = eva_(stats_.best.solution);
306 es_.init(); // customizatin point for strategy-specific initialization
308 for (stats_.gen = 0; !stop_condition(stats_) && !stop; ++stats_.gen)
310 if (shake(stats_.gen))
312 // The `shake` functions clear cached fitness values (they refer to the
313 // previous dataset). So we must recalculate the fitness of the best
315 assert(!stats_.best.solution.empty());
316 stats_.best.score.fitness = eva_(stats_.best.solution);
318 print_progress(0, run_count, true, &from_last_msg);
321 stats_.az = get_stats();
322 log_evolution(run_count);
324 for (unsigned k(0); k < pop_.individuals() && !stop; ++k)
326 if (from_last_msg.elapsed() > std::chrono::seconds(2))
328 print_progress(k, run_count, false, &from_last_msg);
330 stop = term::user_stop();
333 // --------- SELECTION ---------
334 auto parents(es_.selection.run());
336 // --------- CROSSOVER / MUTATION ---------
337 auto off(es_.recombination.run(parents));
339 // --------- REPLACEMENT --------
340 const auto before(stats_.best.score.fitness);
341 es_.replacement.run(parents, off, &stats_);
343 if (stats_.best.score.fitness != before)
344 print_progress(k, run_count, true, &from_last_msg);
347 stats_.elapsed = measure.elapsed();
349 es_.after_generation(); // hook for strategy-specific bookkeeping
350 if (after_generation_callback_)
351 after_generation_callback_(pop_, stats_);
354 vitaINFO << "Elapsed time: "
355 << std::chrono::duration<double>(stats_.elapsed).count()
356 << "s" << std::string(10, ' ');
363/// A shortcut to call the `run` method without a shake function.
365template<class T, template<class> class ES>
366const summary<T> &evolution<T, ES>::run(unsigned run_count)
368 return run(run_count, [](unsigned) { return false; });
372/// \return `true` if object passes the internal consistency check
374template<class T, template<class> class ES>
375bool evolution<T, ES>::is_valid() const
379#endif // include guard