13#if !defined(ULTRA_EVOLUTION_H)
14# error "Don't include this file directly, include the specific .h instead"
17#if !defined(ULTRA_EVOLUTION_TCC)
18#define ULTRA_EVOLUTION_TCC
23enum class phase {evolution, refinement};
27 phase run_phase {phase::evolution};
29 std::atomic<unsigned> planned_steps {0};
30 std::atomic<unsigned> completed_steps {0};
33 timer from_last_msg {};
35 std::size_t helpers {0};
39 run_phase = phase::evolution;
40 planned_steps.store(0, std::memory_order_relaxed);
41 completed_steps.store(0, std::memory_order_relaxed);
45class refinement_schedule
48 explicit refinement_schedule(
const parameters::refinement_parameters &rp)
53 [[nodiscard]]
bool ready(
unsigned stagnation)
const noexcept
55 return cooldown_left_ == 0 && stagnation >= rp_.stagnation_threshold;
58 void after_refinement_attempt(
bool performed)
noexcept
61 cooldown_left_ = rp_.cooldown;
71 const parameters::refinement_parameters &rp_;
73 unsigned cooldown_left_ {};
87 explicit task_group(std::size_t workers) : pool_(workers)
93 template<
class F,
class... Args>
94 requires std::same_as<std::invoke_result_t<F, Args...>,
void>
95 void submit(F &&f, Args&&... args)
97 tasks_.push_back(pool_.submit(std::forward<F>(f),
98 std::forward<Args>(args)...));
105 rethrow_exceptions();
116 template<
class Rep,
class Period>
117 [[nodiscard]]
bool wait_for(
const std::chrono::duration<Rep, Period> &timeout)
119 if (!pool_.wait_for(timeout))
122 rethrow_exceptions();
127 void rethrow_exceptions()
129 std::exception_ptr first_exception;
131 for (
auto &task : tasks_)
138 if (!first_exception)
139 first_exception = std::current_exception();
145 std::rethrow_exception(first_exception);
148 ultra::thread_pool pool_;
149 std::vector<std::future<void>> tasks_ {};
167 std::size_t allocated, std::size_t min_samples_per_eval_thread, E &eva)
169 if constexpr (
requires { eva.set_parallel_evaluation(0, 0); })
171 const auto capacity(hardware_threads());
172 const auto helpers(allocated && allocated < capacity ?
173 (capacity - allocated) / allocated : 0);
175 ultraDEBUG(
"Configure evaluator's parallelism to {}", helpers);
176 if (eva.set_parallel_evaluation(helpers, min_samples_per_eval_thread))
195evolution<E>::evolution(
const problem &prob, E &eva) : pop_(prob), eva_(eva)
199 ultraDEBUG(
"Creating a new instance of evolution class");
213bool evolution<E>::stop_condition()
const
215 const auto planned_generations(pop_.problem().params.evolution.generations);
216 Expects(planned_generations);
219 if (sum_.generation >= planned_generations)
222 if (console.user_stop())
225 return external_stop_source_.stop_requested();
229void evolution<E>::print(message m, internal::print_status &ps)
const
231 if (!emit_messages_ || log::reporting_level > log::lPAROUT)
234 const std::string tags(tag_.empty() ? tag_ :
"[" + tag_ +
"] ");
236 const std::string_view str_phase(ps.run_phase == internal::phase::evolution
237 ?
"evolution" :
"refinement");
239 const auto elapsed(ps.from_start.elapsed());
241 if (m == message::summary)
243 const auto fitness(internal::streamed(sum_.best().fit));
245 ultraPAROUT(
"{}{:>8}{:>8}{:>12}:{:>13}", tags,
246 lexical_cast<std::string>(elapsed), sum_.generation,
253 const auto planned(ps.planned_steps.load(std::memory_order_relaxed));
256 const auto completed(ps.completed_steps.load(std::memory_order_relaxed));
257 perc = std::to_string(std::uintmax_t(100) * completed / planned) +
"%";
261 if (log::reporting_level == log::lPAROUT)
263 static constexpr std::string_view chrs(
"|/-\\");
265 line = std::format(
"{}{} {} {}",
267 chrs[elapsed.count() % chrs.size()],
271 else if (log::reporting_level <= log::lSTDOUT)
274 std::max(std::chrono::duration_cast<std::chrono::seconds>(elapsed),
277 line = std::format(
"{} gen {} {} {}",
278 lexical_cast<std::string>(elapsed),
285 const double gph(3600.0 * sum_.generation / seconds);
286 line += std::format(
" [{}L", pop_.layers());
288 line += std::format(
" x {}T", 1 + ps.helpers);
289 line += std::format(
" | {0:.{1}f}gph]", gph, gph > 2.0 ? 0 : 1);
293 console.status_line(line);
296 if (ps.run_phase == internal::phase::evolution)
297 ps.from_last_msg.restart();
312evolution<E> &evolution<E>::logger(search_log &sl)
319evolution<E> &evolution<E>::refinement(refinement_callback_t f)
321 refinement_callback_ = std::move(f);
326evolution<E> &evolution<E>::messages(
bool m)
noexcept
342evolution<E> &evolution<E>::tag(
const std::string &t)
359evolution<E> &evolution<E>::shake_function(shake_function_callback_t f)
361 shake_ = std::move(f);
372evolution<E> &evolution<E>::after_generation(after_generation_callback_t f)
374 after_generation_callback_ = std::move(f);
379evolution<E> &evolution<E>::on_new_best(on_new_best_callback_t f)
381 sum_.on_new_best(std::move(f));
395evolution<E> &evolution<E>::stop_source(std::stop_source ss)
397 external_stop_source_ = ss;
416template<Population P>
417auto evolution<E>::refinement_tournament(
const P &ref_pop)
419 const auto ¶ms(pop_.problem().params);
420 const auto t_size(params.refinement.tournament_size
421 ? params.refinement.tournament_size
422 : params.evolution.tournament_size);
426 auto best_coord(random::coord(ref_pop));
427 auto best_fit(eva_(ref_pop[best_coord]));
429 for (std::size_t i(1); i < t_size; ++i)
431 const auto c(random::coord(ref_pop));
432 const auto fit(eva_(ref_pop[c]));
456template<Population P>
457bool evolution<E>::perform_refinement(P &ref_pop, internal::print_status &ps)
462 const auto refinement_fraction(pop_.problem().params.refinement.fraction);
463 Expects(in_0_1(refinement_fraction));
465 if (issmall(refinement_fraction))
470 static_cast<std::size_t
>(refinement_fraction * ref_pop.size()));
471 const std::size_t n(std::max(base_n, 1uz));
473 struct refinement_candidate
475 typename P::coord coord;
476 individual_t initial;
480 std::vector<refinement_candidate> candidates;
481 candidates.reserve(n);
483 std::set<typename P::coord> seen;
485 for (std::size_t i(0); i < n; ++i)
487 const auto coord(refinement_tournament(ref_pop));
489 if (seen.insert(coord).second)
490 candidates.push_back({coord, ref_pop[coord], {}});
493 if (candidates.empty())
497 ps.run_phase = internal::phase::refinement;
498 ps.planned_steps.store(
static_cast<unsigned>(candidates.size()),
499 std::memory_order_relaxed);
500 ps.completed_steps.store(0, std::memory_order_relaxed);
502 const auto threads(std::min(candidates.size(), hardware_threads()));
505 threads, pop_.problem().params.min_samples_per_eval_thread, eva_);
507 internal::task_group tasks(threads);
509 for (std::size_t i(0); i < candidates.size(); ++i)
515 refiner optimiser(pop_.problem(), false);
517 auto ind(candidates[i].initial);
520 optimiser.optimise(ind, eva_, refinement_callback_));
523 candidates[i].refined = scored_individual(ind, *fit);
525 ps.completed_steps.fetch_add(1, std::memory_order_relaxed);
526 print(message::status, ps);
532 for (
const auto &result : candidates)
533 if (!result.refined.empty())
535 if (sum_.update_if_better(result.refined))
536 print(message::summary, ps);
538 ref_pop[result.coord] = result.refined.ind;
542 ps.run_phase = internal::phase::evolution;
544 pop_.layers(), pop_.problem().params.min_samples_per_eval_thread,
572template<
template<
class>
class ES>
574 typename evolution<E>::fitness_t> evolution<E>::run()
576 Expects(sum_.generation == 0);
578 const auto ¶ms(pop_.problem().params);
580 if (params.needs_init())
581 throw std::logic_error(
"Parameters still contain auto-tune values. "
582 "Call parameters::init() (or use src::search) "
583 "before evolution::run().");
585 using namespace std::chrono_literals;
587 std::stop_source source;
591 std::stop_callback external_stop_callback(
592 external_stop_source_.get_token(),
593 [&source] { source.request_stop(); });
595 if (source.stop_requested())
598 ES<E> strategy(pop_.problem(), eva_);
603 internal::print_status ps;
604 internal::refinement_schedule ref_sched(params.refinement);
610 const auto evolve_subpop(
611 [&, stop_token = source.get_token()](
auto subpop_it)
613 auto evolve(strategy.operations(pop_, subpop_it,
614 sum_.starting_status()));
618 auto cycles(subpop_it->safe_size());
620 ps.planned_steps.fetch_add(cycles, std::memory_order_relaxed);
623 if (!stop_token.stop_requested())
626 ps.completed_steps.fetch_add(1, std::memory_order_relaxed);
630 const auto print_and_update_if_better(
633 if (progress_best < candidate)
635 progress_best = candidate;
636 print(message::summary, ps);
643 ultraDEBUG(
"Calling evolution_strategy init method");
646 internal::task_group tasks(strategy.max_parallelism(pop_));
648 while (!source.stop_requested() && !stop_condition())
652 if (shake_ && shake_(sum_.generation) == evaluation_context::changed)
656 if (
const auto best(sum_.best()); !best.empty())
658 sum_.set_best_fitness(eva_(best.ind));
659 progress_best = sum_.best();
664 pop_.layers(), pop_.problem().params.min_samples_per_eval_thread, eva_);
666 ultraDEBUG(
"Launching tasks for generation {}", sum_.generation);
667 const auto subpops(pop_.range_of_layers());
669 for (
auto l(subpops.begin()); l != subpops.end(); ++l)
670 tasks.submit(evolve_subpop, l);
672 ultraDEBUG(
"Tasks running");
674 while (!tasks.wait_for(10ms))
676 if (ps.from_last_msg.elapsed() > 2s
677 && !print_and_update_if_better(sum_.best()))
678 print(message::status, ps);
680 if (!source.stop_requested() && stop_condition()) [[unlikely]]
682 source.request_stop();
683 ultraDEBUG(
"Sending closing message to tasks");
687 print_and_update_if_better(sum_.best());
689 if (source.stop_requested())
692 if constexpr (RefinementStrategy<ES<E>,
decltype(pop_)>)
694 if (refinement_callback_)
696 if (ref_sched.ready(sum_.stagnation()))
698 bool performed(
false);
700 if (
auto *ref_pop = strategy.refinement_subgroup(pop_))
701 performed = perform_refinement(*ref_pop, ps);
703 ref_sched.after_refinement_attempt(performed);
708 progress_best = sum_.best();
715 sum_.az = analyzer(pop_, eva_);
717 search_log_->save_snapshot(pop_, sum_);
719 strategy.after_generation(pop_, sum_);
720 if (after_generation_callback_)
721 after_generation_callback_(pop_, sum_);
726 sum_.elapsed = ps.from_start.elapsed();
729 ultraINFO(
"Evolution completed at generation: {}. Elapsed time: {}",
731 lexical_cast<std::string>(ps.from_start.elapsed()));
742bool evolution<E>::is_valid()
const
744 return pop_.is_valid();
Definition evolution_summary.h:33
std::size_t set_evaluator_parallelism(std::size_t allocated, std::size_t min_samples_per_eval_thread, E &eva)
Definition evolution.tcc:166
void invalidate_cache_if_supported(E &eva)
Definition evaluation_context.h:48
static constexpr std::size_t max_tournament_size
Definition parameters.h:180
Definition scored_individual.h:48