add ability to do steady state GPs. Untested currently.

main
Brett 2025-04-01 20:55:40 -04:00
parent 577f3d613c
commit 08e44fb4bd
4 changed files with 1007 additions and 893 deletions

View File

@ -27,7 +27,7 @@ macro(compile_options target_name)
sanitizers(${target_name}) sanitizers(${target_name})
endmacro() endmacro()
project(blt-gp VERSION 0.3.34) project(blt-gp VERSION 0.4.0)
include(CTest) include(CTest)

View File

@ -46,8 +46,7 @@ namespace blt::gp::example
fitness.raw_fitness += diff; fitness.raw_fitness += diff;
if (diff <= 0.01) if (diff <= 0.01)
fitness.hits++; fitness.hits++;
} } else
else
fitness.raw_fitness += value_cutoff; fitness.raw_fitness += value_cutoff;
} }
fitness.standardized_fitness = fitness.raw_fitness; fitness.standardized_fitness = fitness.raw_fitness;
@ -75,8 +74,7 @@ namespace blt::gp::example
fitness_case = {x, y}; fitness_case = {x, y};
} }
fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i) fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i) {
{
return fitness_function(t, f, i); return fitness_function(t, f, i);
}; };
} }
@ -86,27 +84,38 @@ namespace blt::gp::example
BLT_DEBUG("Setup Types and Operators"); BLT_DEBUG("Setup Types and Operators");
static operation_t add{ static operation_t add{
// this is the function used by the operation // this is the function used by the operation
[](const float a, const float b) [](const float a, const float b) {
{
return a + b; return a + b;
}, },
// this name is optional and is used if you print an individual // this name is optional and is used if you print an individual
"add" "add"
}; };
static operation_t sub([](const float a, const float b) { return a - b; }, "sub"); static operation_t sub([](const float a, const float b) {
static operation_t mul([](const float a, const float b) { return a * b; }, "mul"); return a - b;
static operation_t pro_div([](const float a, const float b) { return b == 0.0f ? 0.0f : a / b; }, "div"); }, "sub");
static operation_t op_sin([](const float a) { return std::sin(a); }, "sin"); static operation_t mul([](const float a, const float b) {
static operation_t op_cos([](const float a) { return std::cos(a); }, "cos"); return a * b;
static operation_t op_exp([](const float a) { return std::exp(a); }, "exp"); }, "mul");
static operation_t op_log([](const float a) { return a <= 0.0f ? 0.0f : std::log(a); }, "log"); static operation_t pro_div([](const float a, const float b) {
static auto lit = operation_t([this]() return b == 0.0f ? 0.0f : a / b;
{ }, "div");
static operation_t op_sin([](const float a) {
return std::sin(a);
}, "sin");
static operation_t op_cos([](const float a) {
return std::cos(a);
}, "cos");
static operation_t op_exp([](const float a) {
return std::exp(a);
}, "exp");
static operation_t op_log([](const float a) {
return a <= 0.0f ? 0.0f : std::log(a);
}, "log");
static auto lit = operation_t([this]() {
return program.get_random().get_float(-1.0f, 1.0f); return program.get_random().get_float(-1.0f, 1.0f);
}, "lit").set_ephemeral(); }, "lit").set_ephemeral();
static operation_t op_x([](const context& context) static operation_t op_x([](const context& context) {
{
return context.x; return context.x;
}, "x"); }, "x");
@ -125,8 +134,9 @@ namespace blt::gp::example
mutation_sel = &sel; mutation_sel = &sel;
if (reproduction_sel == nullptr) if (reproduction_sel == nullptr)
reproduction_sel = &sel; reproduction_sel = &sel;
program.generate_population(program.get_typesystem().get_type<float>().id(), fitness_function_ref, *crossover_sel, *mutation_sel, program.generate_initial_population(program.get_typesystem().get_type<float>().id());
*reproduction_sel); program.setup_generational_evaluation(fitness_function_ref, *crossover_sel,
*mutation_sel, *reproduction_sel);
} }
void run_generation_loop() void run_generation_loop()
@ -134,12 +144,12 @@ namespace blt::gp::example
BLT_DEBUG("Begin Generation Loop"); BLT_DEBUG("Begin Generation Loop");
while (!program.should_terminate()) while (!program.should_terminate())
{ {
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto cross = crossover_calls.start_measurement(); auto cross = crossover_calls.start_measurement();
auto mut = mutation_calls.start_measurement(); auto mut = mutation_calls.start_measurement();
auto repo = reproduction_calls.start_measurement(); auto repo = reproduction_calls.start_measurement();
#endif #endif
BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation()); BLT_TRACE("------------\\{Begin Generation {}}------------", program.get_current_generation());
BLT_TRACE("Creating next generation"); BLT_TRACE("Creating next generation");
program.create_next_generation(); program.create_next_generation();
BLT_TRACE("Move to next generation"); BLT_TRACE("Move to next generation");
@ -147,17 +157,17 @@ namespace blt::gp::example
BLT_TRACE("Evaluate Fitness"); BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness(); program.evaluate_fitness();
const auto& stats = program.get_population_stats(); const auto& stats = program.get_population_stats();
BLT_TRACE("Avg Fit: %lf, Best Fit: %lf, Worst Fit: %lf, Overall Fit: %lf", BLT_TRACE("Avg Fit: {:0.6f}, Best Fit: {:0.6f}, Worst Fit: {:0.6f}, Overall Fit: {:0.6f}", stats.average_fitness.load(std::memory_order_relaxed),
stats.average_fitness.load(std::memory_order_relaxed), stats.best_fitness.load(std::memory_order_relaxed), stats.best_fitness.load(std::memory_order_relaxed), stats.worst_fitness.load(std::memory_order_relaxed),
stats.worst_fitness.load(std::memory_order_relaxed), stats.overall_fitness.load(std::memory_order_relaxed)); stats.overall_fitness.load(std::memory_order_relaxed));
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.stop_measurement(cross); crossover_calls.stop_measurement(cross);
mutation_calls.stop_measurement(mut); mutation_calls.stop_measurement(mut);
reproduction_calls.stop_measurement(repo); reproduction_calls.stop_measurement(repo);
const auto total = (cross.get_call_difference() * 2) + mut.get_call_difference() + repo.get_call_difference(); const auto total = (cross.get_call_difference() * 2) + mut.get_call_difference() + repo.get_call_difference();
BLT_TRACE("Calls Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total); BLT_TRACE("Calls Crossover: {}, Mutation {}, Reproduction {}; {}", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total);
BLT_TRACE("Value Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_value_difference(), mut.get_value_difference(), repo.get_value_difference(), (cross.get_value_difference() * 2 + mut.get_value_difference() + repo.get_value_difference()) - total); BLT_TRACE("Value Crossover: {}, Mutation {}, Reproduction {}; {}", cross.get_value_difference(), mut.get_value_difference(), repo.get_value_difference(), (cross.get_value_difference() * 2 + mut.get_value_difference() + repo.get_value_difference()) - total);
#endif #endif
BLT_TRACE("----------------------------------------------"); BLT_TRACE("----------------------------------------------");
std::cout << std::endl; std::cout << std::endl;
} }
@ -171,7 +181,7 @@ namespace blt::gp::example
for (auto& i_ref : best) for (auto& i_ref : best)
{ {
auto& i = i_ref.get(); auto& i = i_ref.get();
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness); BLT_DEBUG("Fitness: {:0.6f}, stand: {:0.6f}, raw: {:0.6f}", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(std::cout); i.tree.print(std::cout);
std::cout << "\n"; std::cout << "\n";
} }

View File

@ -19,7 +19,6 @@
#ifndef BLT_GP_PROGRAM_H #ifndef BLT_GP_PROGRAM_H
#define BLT_GP_PROGRAM_H #define BLT_GP_PROGRAM_H
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
@ -124,9 +123,8 @@ namespace blt::gp
blt::size_t largest_returns = 0; blt::size_t largest_returns = 0;
blt::u32 largest_argc = 0; blt::u32 largest_argc = 0;
operator_metadata_t meta; operator_metadata_t meta;
((meta = add_operator(operators), largest_argc = std::max(meta.argc.argc, largest_argc), ((meta = add_operator(operators), largest_argc = std::max(meta.argc.argc, largest_argc), largest_args =
largest_args = std::max(meta.arg_size_bytes, largest_args), largest_returns = std::max(meta.return_size_bytes, std::max(meta.arg_size_bytes, largest_args), largest_returns = std::max(meta.return_size_bytes, largest_returns)), ...);
largest_returns)), ...);
// largest = largest * largest_argc; // largest = largest * largest_argc;
size_t largest = largest_args * largest_argc * largest_returns * largest_argc; size_t largest = largest_args * largest_argc * largest_returns * largest_argc;
@ -177,16 +175,14 @@ namespace blt::gp
"(that is all input types are terminals) for return type " + std::to_string(return_type)).c_str()); "(that is all input types are terminals) for return type " + std::to_string(return_type)).c_str());
} }
std::sort(ordered_terminals.begin(), ordered_terminals.end(), [](const auto& a, const auto& b) std::sort(ordered_terminals.begin(), ordered_terminals.end(), [](const auto& a, const auto& b) {
{
return a.second > b.second; return a.second > b.second;
}); });
auto first_size = *ordered_terminals.begin(); auto first_size = *ordered_terminals.begin();
auto iter = ordered_terminals.begin(); auto iter = ordered_terminals.begin();
while (++iter != ordered_terminals.end() && iter->second == first_size.second) while (++iter != ordered_terminals.end() && iter->second == first_size.second)
{ {}
}
ordered_terminals.erase(iter, ordered_terminals.end()); ordered_terminals.erase(iter, ordered_terminals.end());
@ -242,20 +238,17 @@ namespace blt::gp
meta.argc = info.argc; meta.argc = info.argc;
storage.operator_metadata.push_back(meta); storage.operator_metadata.push_back(meta);
storage.print_funcs.push_back([&op](std::ostream& out, stack_allocator& stack) storage.print_funcs.push_back([&op](std::ostream& out, stack_allocator& stack) {
{
if constexpr (blt::meta::is_streamable_v<Return>) if constexpr (blt::meta::is_streamable_v<Return>)
{ {
out << stack.from<Return>(0); out << stack.from<Return>(0);
(void)(op); // remove warning (void) (op); // remove warning
} } else
else
{ {
out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]"; out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]";
} }
}); });
storage.destroy_funcs.push_back([](const detail::destroy_t type, u8* data) storage.destroy_funcs.push_back([](const detail::destroy_t type, u8* data) {
{
switch (type) switch (type)
{ {
case detail::destroy_t::PTR: case detail::destroy_t::PTR:
@ -294,13 +287,17 @@ namespace blt::gp
* *
* @param seed * @param seed
*/ */
explicit gp_program(blt::u64 seed): seed_func([seed] { return seed; }) explicit gp_program(blt::u64 seed): seed_func([seed] {
return seed;
})
{ {
create_threads(); create_threads();
selection_probabilities.update(config); selection_probabilities.update(config);
} }
explicit gp_program(blt::u64 seed, const prog_config_t& config): seed_func([seed] { return seed; }), config(config) explicit gp_program(blt::u64 seed, const prog_config_t& config): seed_func([seed] {
return seed;
}), config(config)
{ {
create_threads(); create_threads();
selection_probabilities.update(config); selection_probabilities.update(config);
@ -337,16 +334,16 @@ namespace blt::gp
void create_next_generation() void create_next_generation()
{ {
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto gen_alloc = blt::gp::tracker.start_measurement(); auto gen_alloc = blt::gp::tracker.start_measurement();
#endif #endif
// should already be empty // should already be empty
thread_helper.next_gen_left.store(config.population_size, std::memory_order_release); thread_helper.next_gen_left.store(selection_probabilities.replacement_amount.value_or(config.population_size), std::memory_order_release);
(*thread_execution_service)(0); (*thread_execution_service)(0);
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
blt::gp::tracker.stop_measurement(gen_alloc); blt::gp::tracker.stop_measurement(gen_alloc);
gen_alloc.pretty_print("Generation"); gen_alloc.pretty_print("Generation");
#endif #endif
} }
void next_generation() void next_generation()
@ -357,11 +354,11 @@ namespace blt::gp
void evaluate_fitness() void evaluate_fitness()
{ {
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto fitness_alloc = blt::gp::tracker.start_measurement(); auto fitness_alloc = blt::gp::tracker.start_measurement();
#endif #endif
evaluate_fitness_internal(); evaluate_fitness_internal();
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
blt::gp::tracker.stop_measurement(fitness_alloc); blt::gp::tracker.stop_measurement(fitness_alloc);
fitness_alloc.pretty_print("Fitness"); fitness_alloc.pretty_print("Fitness");
evaluation_calls.call(); evaluation_calls.call();
@ -370,14 +367,15 @@ namespace blt::gp
{ {
evaluation_allocations.call(fitness_alloc.getAllocatedByteDifference()); evaluation_allocations.call(fitness_alloc.getAllocatedByteDifference());
} }
#endif #endif
} }
void reset_program(type_id root_type, bool eval_fitness_now = true) void reset_program(type_id root_type, bool eval_fitness_now = true)
{ {
current_generation = 0; current_generation = 0;
current_pop = config.pop_initializer.get().generate( current_pop = config.pop_initializer.get().generate({
{*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size}); *this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size
});
next_pop = population_t(current_pop); next_pop = population_t(current_pop);
BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size, BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str()); ("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
@ -392,6 +390,18 @@ namespace blt::gp
thread_helper.lifetime_over = true; thread_helper.lifetime_over = true;
} }
void generate_initial_population(const type_id root_type)
{
current_pop = config.pop_initializer.get().generate({
*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size
});
next_pop = population_t(current_pop);
BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
}
/** /**
* takes in a reference to a function for the fitness evaluation function (must return a value convertable to double) * takes in a reference to a function for the fitness evaluation function (must return a value convertable to double)
* The lambda must accept a tree for evaluation, and an index (current tree) * The lambda must accept a tree for evaluation, and an index (current tree)
@ -403,58 +413,28 @@ namespace blt::gp
* NOTE: 0 is considered the best, in terms of standardized fitness * NOTE: 0 is considered the best, in terms of standardized fitness
*/ */
template <typename FitnessFunc, typename Crossover, typename Mutation, typename Reproduction> template <typename FitnessFunc, typename Crossover, typename Mutation, typename Reproduction>
void generate_population(type_id root_type, FitnessFunc& fitness_function, void setup_generational_evaluation(FitnessFunc& fitness_function, Crossover& crossover_selection, Mutation& mutation_selection,
Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection, Reproduction& reproduction_selection, bool eval_fitness_now = true)
bool eval_fitness_now = true)
{ {
using LambdaReturn = std::invoke_result_t<decltype(fitness_function), const tree_t&, fitness_t&, size_t>;
current_pop = config.pop_initializer.get().generate(
{*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size});
next_pop = population_t(current_pop);
BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
if (config.threads == 1) if (config.threads == 1)
{ {
BLT_INFO("Starting with single thread variant!"); BLT_INFO("Starting generational with single thread variant!");
thread_execution_service = std::unique_ptr<std::function<void(size_t)>>(new std::function( thread_execution_service = std::unique_ptr<std::function<void(size_t)>>(new std::function(
[this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t) [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t) {
{ single_threaded_fitness_eval<FitnessFunc>()(fitness_function);
if (thread_helper.evaluation_left > 0)
if (thread_helper.next_gen_left > 0)
{ {
current_stats.normalized_fitness.clear(); current_stats.normalized_fitness.clear();
double sum_of_prob = 0; double sum_of_prob = 0;
for (const auto& [index, ind] : blt::enumerate(current_pop.get_individuals())) for (const auto& ind : current_pop)
{ {
ind.fitness = {}; const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
if constexpr (std::is_same_v<LambdaReturn, bool> || std::is_convertible_v<LambdaReturn, bool>)
{
if (fitness_function(ind.tree, ind.fitness, index))
fitness_should_exit = true;
}
else
fitness_function(ind.tree, ind.fitness, index);
if (ind.fitness.adjusted_fitness > current_stats.best_fitness)
current_stats.best_fitness = ind.fitness.adjusted_fitness;
if (ind.fitness.adjusted_fitness < current_stats.worst_fitness)
current_stats.worst_fitness = ind.fitness.adjusted_fitness;
current_stats.overall_fitness = current_stats.overall_fitness + ind.fitness.adjusted_fitness;
}
for (auto& ind : current_pop)
{
auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
current_stats.normalized_fitness.push_back(sum_of_prob + prob); current_stats.normalized_fitness.push_back(sum_of_prob + prob);
sum_of_prob += prob; sum_of_prob += prob;
} }
thread_helper.evaluation_left = 0;
} const auto args = get_selector_args();
if (thread_helper.next_gen_left > 0)
{
auto args = get_selector_args();
crossover_selection.pre_process(*this, current_pop); crossover_selection.pre_process(*this, current_pop);
mutation_selection.pre_process(*this, current_pop); mutation_selection.pre_process(*this, current_pop);
@ -474,73 +454,16 @@ namespace blt::gp
thread_helper.next_gen_left = 0; thread_helper.next_gen_left = 0;
} }
})); }));
} } else
else
{ {
BLT_INFO("Starting thread execution service!"); BLT_INFO("Starting generational thread execution service!");
std::scoped_lock lock(thread_helper.thread_function_control); std::scoped_lock lock(thread_helper.thread_function_control);
thread_execution_service = std::unique_ptr<std::function<void(blt::size_t)>>(new std::function( thread_execution_service = std::unique_ptr<std::function<void(size_t)>>(new std::function(
[this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t id) [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](const size_t id) {
{
thread_helper.barrier.wait(); thread_helper.barrier.wait();
if (thread_helper.evaluation_left > 0) multi_threaded_fitness_eval<FitnessFunc>()(fitness_function);
{
while (thread_helper.evaluation_left > 0)
{
blt::size_t size = 0;
blt::size_t begin = 0;
blt::size_t end = thread_helper.evaluation_left.load(std::memory_order_relaxed);
do
{
size = std::min(end, config.evaluation_size);
begin = end - size;
}
while (!thread_helper.evaluation_left.compare_exchange_weak(end, end - size,
std::memory_order::memory_order_relaxed,
std::memory_order::memory_order_relaxed));
for (blt::size_t i = begin; i < end; i++)
{
auto& ind = current_pop.get_individuals()[i];
ind.fitness = {};
if constexpr (std::is_same_v<LambdaReturn, bool> || std::is_convertible_v<LambdaReturn, bool>)
{
auto result = fitness_function(ind.tree, ind.fitness, i);
if (result)
fitness_should_exit = true;
}
else
{
fitness_function(ind.tree, ind.fitness, i);
}
auto old_best = current_stats.best_fitness.load(std::memory_order_relaxed);
while (ind.fitness.adjusted_fitness > old_best &&
!current_stats.best_fitness.compare_exchange_weak(old_best, ind.fitness.adjusted_fitness,
std::memory_order_relaxed,
std::memory_order_relaxed))
{
}
auto old_worst = current_stats.worst_fitness.load(std::memory_order_relaxed);
while (ind.fitness.adjusted_fitness < old_worst &&
!current_stats.worst_fitness.compare_exchange_weak(old_worst, ind.fitness.adjusted_fitness,
std::memory_order_relaxed,
std::memory_order_relaxed))
{
}
auto old_overall = current_stats.overall_fitness.load(std::memory_order_relaxed);
while (!current_stats.overall_fitness.compare_exchange_weak(old_overall,
ind.fitness.adjusted_fitness + old_overall,
std::memory_order_relaxed,
std::memory_order_relaxed))
{
}
}
}
}
if (thread_helper.next_gen_left > 0) if (thread_helper.next_gen_left > 0)
{ {
thread_helper.barrier.wait(); thread_helper.barrier.wait();
@ -549,9 +472,9 @@ namespace blt::gp
{ {
current_stats.normalized_fitness.clear(); current_stats.normalized_fitness.clear();
double sum_of_prob = 0; double sum_of_prob = 0;
for (auto& ind : current_pop) for (const auto& ind : current_pop)
{ {
auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness); const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
current_stats.normalized_fitness.push_back(sum_of_prob + prob); current_stats.normalized_fitness.push_back(sum_of_prob + prob);
sum_of_prob += prob; sum_of_prob += prob;
} }
@ -575,10 +498,8 @@ namespace blt::gp
{ {
size = std::min(end, config.evaluation_size); size = std::min(end, config.evaluation_size);
begin = end - size; begin = end - size;
} } while (!thread_helper.next_gen_left.compare_exchange_weak(
while (!thread_helper.next_gen_left.compare_exchange_weak(end, end - size, end, end - size, std::memory_order::memory_order_relaxed, std::memory_order::memory_order_relaxed));
std::memory_order::memory_order_relaxed,
std::memory_order::memory_order_relaxed));
while (begin != end) while (begin != end)
{ {
@ -599,6 +520,116 @@ namespace blt::gp
evaluate_fitness_internal(); evaluate_fitness_internal();
} }
template <typename FitnessFunc, typename SelectionStrat, typename Crossover, typename Mutation, typename Reproduction>
void setup_steady_state_evaluation(FitnessFunc& fitness_function, SelectionStrat& replacement_strategy, size_t replacement_amount,
Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
const bool eval_fitness_now = true)
{
selection_probabilities.replacement_amount = replacement_amount;
if (config.threads == 1)
{
BLT_INFO("Starting steady state with single thread variant!");
thread_execution_service = std::unique_ptr<std::function<void(size_t)>>(new std::function(
[this, &fitness_function, &replacement_strategy, &crossover_selection, &mutation_selection, &reproduction_selection](size_t) {
single_threaded_fitness_eval<FitnessFunc>()(fitness_function);
if (thread_helper.next_gen_left > 0)
{
current_stats.normalized_fitness.clear();
double sum_of_prob = 0;
for (const auto& ind : current_pop)
{
const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
current_stats.normalized_fitness.push_back(sum_of_prob + prob);
sum_of_prob += prob;
}
next_pop = population_t(current_pop);
replacement_strategy.pre_process(*this, next_pop);
crossover_selection.pre_process(*this, current_pop);
mutation_selection.pre_process(*this, current_pop);
reproduction_selection.pre_process(*this, current_pop);
while (thread_helper.next_gen_left > 0)
{
tree_t& c1 = replacement_strategy.select(*this, next_pop);
tree_t* c2 = nullptr;
if (thread_helper.next_gen_left > 1)
while (c2 != &c1)
c2 = &replacement_strategy.select(*this, next_pop);
thread_helper.next_gen_left -= perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1,
c2);
}
thread_helper.next_gen_left = 0;
}
}));
} else
{
BLT_INFO("Starting steady state thread execution service!");
std::scoped_lock lock(thread_helper.thread_function_control);
thread_execution_service = std::unique_ptr<std::function<void(size_t)>>(new std::function(
[this, &fitness_function, &replacement_strategy, &crossover_selection, &mutation_selection, &reproduction_selection](
const size_t id) {
thread_helper.barrier.wait();
multi_threaded_fitness_eval<FitnessFunc>()(fitness_function);
if (thread_helper.next_gen_left > 0)
{
thread_helper.barrier.wait();
if (id == 0)
{
current_stats.normalized_fitness.clear();
double sum_of_prob = 0;
for (const auto& ind : current_pop)
{
const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
current_stats.normalized_fitness.push_back(sum_of_prob + prob);
sum_of_prob += prob;
}
current_pop = population_t(next_pop);
replacement_strategy.pre_process(*this, next_pop);
crossover_selection.pre_process(*this, current_pop);
if (&crossover_selection != &mutation_selection)
mutation_selection.pre_process(*this, current_pop);
if (&crossover_selection != &reproduction_selection)
reproduction_selection.pre_process(*this, current_pop);
}
thread_helper.barrier.wait();
while (thread_helper.next_gen_left > 0)
{
size_t size = 0;
size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed);
do
{
size = std::min(end, config.evaluation_size);
} while (!thread_helper.next_gen_left.compare_exchange_weak(
end, end - size, std::memory_order::memory_order_relaxed, std::memory_order::memory_order_relaxed));
while (size > 0)
{
tree_t& c1 = replacement_strategy.select(*this, next_pop);
tree_t* c2 = nullptr;
if (thread_helper.next_gen_left > 1)
while (c2 != &c1)
c2 = &replacement_strategy.select(*this, next_pop);
size -= perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
}
}
}
thread_helper.barrier.wait();
}));
thread_helper.thread_function_condition.notify_all();
}
if (eval_fitness_now)
evaluate_fitness_internal();
}
[[nodiscard]] bool should_terminate() const [[nodiscard]] bool should_terminate() const
{ {
return current_generation >= config.max_generations || fitness_should_exit; return current_generation >= config.max_generations || fitness_should_exit;
@ -666,7 +697,7 @@ namespace blt::gp
return storage.destroy_funcs[id]; return storage.destroy_funcs[id];
} }
[[nodiscard]] std::optional<std::string_view> get_name(operator_id id) [[nodiscard]] std::optional<std::string_view> get_name(operator_id id) const
{ {
return storage.names[id]; return storage.names[id];
} }
@ -727,8 +758,7 @@ namespace blt::gp
for (const auto& [index, value] : blt::enumerate(current_pop.get_individuals())) for (const auto& [index, value] : blt::enumerate(current_pop.get_individuals()))
values.emplace_back(index, value.fitness.adjusted_fitness); values.emplace_back(index, value.fitness.adjusted_fitness);
std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) {
{
return a.second > b.second; return a.second > b.second;
}); });
@ -746,8 +776,7 @@ namespace blt::gp
return convert_array<std::array<std::reference_wrapper<individual_t>, size>>(get_best_indexes<size>(), return convert_array<std::array<std::reference_wrapper<individual_t>, size>>(get_best_indexes<size>(),
[this](auto&& arr, blt::size_t index) -> tree_t& { [this](auto&& arr, blt::size_t index) -> tree_t& {
return current_pop.get_individuals()[arr[index]].tree; return current_pop.get_individuals()[arr[index]].tree;
}, }, std::make_integer_sequence<blt::size_t, size>());
std::make_integer_sequence<blt::size_t, size>());
} }
template <blt::size_t size> template <blt::size_t size>
@ -756,11 +785,88 @@ namespace blt::gp
return convert_array<std::array<std::reference_wrapper<individual_t>, size>>(get_best_indexes<size>(), return convert_array<std::array<std::reference_wrapper<individual_t>, size>>(get_best_indexes<size>(),
[this](auto&& arr, blt::size_t index) -> individual_t& { [this](auto&& arr, blt::size_t index) -> individual_t& {
return current_pop.get_individuals()[arr[index]]; return current_pop.get_individuals()[arr[index]];
}, }, std::make_integer_sequence<blt::size_t, size>());
std::make_integer_sequence<blt::size_t, size>());
} }
private: private:
template <typename FitnessFunc>
auto single_threaded_fitness_eval()
{
return [this](FitnessFunc& fitness_function) {
if (thread_helper.evaluation_left > 0)
{
current_stats.normalized_fitness.clear();
double sum_of_prob = 0;
perform_fitness_function(0, current_pop.get_individuals().size(), fitness_function);
for (const auto& ind : current_pop)
{
const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
current_stats.normalized_fitness.push_back(sum_of_prob + prob);
sum_of_prob += prob;
}
thread_helper.evaluation_left = 0;
}
};
}
template <typename FitnessFunc>
auto multi_threaded_fitness_eval()
{
return [this](FitnessFunc& fitness_function) {
if (thread_helper.evaluation_left > 0)
{
while (thread_helper.evaluation_left > 0)
{
size_t size = 0;
size_t begin = 0;
size_t end = thread_helper.evaluation_left.load(std::memory_order_relaxed);
do
{
size = std::min(end, config.evaluation_size);
begin = end - size;
} while (!thread_helper.evaluation_left.compare_exchange_weak(end, end - size, std::memory_order::memory_order_relaxed,
std::memory_order::memory_order_relaxed));
perform_fitness_function(begin, end, fitness_function);
}
}
};
}
template <typename FitnessFunction>
void perform_fitness_function(const size_t begin, const size_t end, FitnessFunction& fitness_function)
{
using LambdaReturn = std::invoke_result_t<decltype(fitness_function), const tree_t&, fitness_t&, size_t>;
for (size_t i = begin; i < end; i++)
{
auto& ind = current_pop.get_individuals()[i];
ind.fitness = {};
if constexpr (std::is_same_v<LambdaReturn, bool> || std::is_convertible_v<LambdaReturn, bool>)
{
if (fitness_function(ind.tree, ind.fitness, i))
fitness_should_exit = true;
} else
{
fitness_function(ind.tree, ind.fitness, i);
}
auto old_best = current_stats.best_fitness.load(std::memory_order_relaxed);
while (ind.fitness.adjusted_fitness > old_best && !current_stats.best_fitness.compare_exchange_weak(
old_best, ind.fitness.adjusted_fitness, std::memory_order_relaxed, std::memory_order_relaxed))
{}
auto old_worst = current_stats.worst_fitness.load(std::memory_order_relaxed);
while (ind.fitness.adjusted_fitness < old_worst && !current_stats.worst_fitness.compare_exchange_weak(
old_worst, ind.fitness.adjusted_fitness, std::memory_order_relaxed, std::memory_order_relaxed))
{}
auto old_overall = current_stats.overall_fitness.load(std::memory_order_relaxed);
while (!current_stats.overall_fitness.compare_exchange_weak(old_overall, ind.fitness.adjusted_fitness + old_overall,
std::memory_order_relaxed, std::memory_order_relaxed))
{}
}
}
template <typename Crossover, typename Mutation, typename Reproduction> template <typename Crossover, typename Mutation, typename Reproduction>
size_t perform_selection(Crossover& crossover, Mutation& mutation, Reproduction& reproduction, tree_t& c1, tree_t* c2) size_t perform_selection(Crossover& crossover, Mutation& mutation, Reproduction& reproduction, tree_t& c1, tree_t* c2)
{ {
@ -769,9 +875,9 @@ namespace blt::gp
auto ptr = c2; auto ptr = c2;
if (ptr == nullptr) if (ptr == nullptr)
ptr = &tree_t::get_thread_local(*this); ptr = &tree_t::get_thread_local(*this);
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local(); auto state = tracker.start_measurement_thread_local();
#endif #endif
const tree_t* p1; const tree_t* p1;
const tree_t* p2; const tree_t* p2;
size_t runs = 0; size_t runs = 0;
@ -788,12 +894,11 @@ namespace blt::gp
if (++runs >= config.crossover.get().get_config().max_crossover_iterations) if (++runs >= config.crossover.get().get_config().max_crossover_iterations)
return 0; return 0;
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.value(1); crossover_calls.value(1);
#endif #endif
} } while (!config.crossover.get().apply(*this, *p1, *p2, c1, *ptr));
while (!config.crossover.get().apply(*this, *p1, *p2, c1, *ptr)); #ifdef BLT_TRACK_ALLOCATIONS
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state); tracker.stop_measurement_thread_local(state);
crossover_calls.call(); crossover_calls.call();
if (state.getAllocatedByteDifference() != 0) if (state.getAllocatedByteDifference() != 0)
@ -801,7 +906,7 @@ namespace blt::gp
crossover_allocations.call(state.getAllocatedByteDifference()); crossover_allocations.call(state.getAllocatedByteDifference());
crossover_allocations.set_value(std::max(crossover_allocations.get_value(), state.getAllocatedByteDifference())); crossover_allocations.set_value(std::max(crossover_allocations.get_value(), state.getAllocatedByteDifference()));
} }
#endif #endif
if (c2 == nullptr) if (c2 == nullptr)
{ {
tree_t::get_thread_local(*this).clear(*this); tree_t::get_thread_local(*this).clear(*this);
@ -811,21 +916,20 @@ namespace blt::gp
} }
if (get_random().choice(selection_probabilities.mutation_chance)) if (get_random().choice(selection_probabilities.mutation_chance))
{ {
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local(); auto state = tracker.start_measurement_thread_local();
#endif #endif
// mutation // mutation
const tree_t* p; const tree_t* p;
do do
{ {
p = &mutation.select(*this, current_pop); p = &mutation.select(*this, current_pop);
c1.copy_fast(*p); c1.copy_fast(*p);
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
mutation_calls.value(1); mutation_calls.value(1);
#endif #endif
} } while (!config.mutator.get().apply(*this, *p, c1));
while (!config.mutator.get().apply(*this, *p, c1)); #ifdef BLT_TRACK_ALLOCATIONS
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state); tracker.stop_measurement_thread_local(state);
mutation_calls.call(); mutation_calls.call();
if (state.getAllocationDifference() != 0) if (state.getAllocationDifference() != 0)
@ -833,17 +937,17 @@ namespace blt::gp
mutation_allocations.call(state.getAllocatedByteDifference()); mutation_allocations.call(state.getAllocatedByteDifference());
mutation_allocations.set_value(std::max(mutation_allocations.get_value(), state.getAllocatedByteDifference())); mutation_allocations.set_value(std::max(mutation_allocations.get_value(), state.getAllocatedByteDifference()));
} }
#endif #endif
return 1; return 1;
} }
if (selection_probabilities.reproduction_chance > 0) if (selection_probabilities.reproduction_chance > 0)
{ {
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local(); auto state = tracker.start_measurement_thread_local();
#endif #endif
// reproduction // reproduction
c1.copy_fast(reproduction.select(*this, current_pop)); c1.copy_fast(reproduction.select(*this, current_pop));
#ifdef BLT_TRACK_ALLOCATIONS #ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state); tracker.stop_measurement_thread_local(state);
reproduction_calls.call(); reproduction_calls.call();
reproduction_calls.value(1); reproduction_calls.value(1);
@ -852,7 +956,7 @@ namespace blt::gp
reproduction_allocations.call(state.getAllocatedByteDifference()); reproduction_allocations.call(state.getAllocatedByteDifference());
reproduction_allocations.set_value(std::max(reproduction_allocations.get_value(), state.getAllocatedByteDifference())); reproduction_allocations.set_value(std::max(reproduction_allocations.get_value(), state.getAllocatedByteDifference()));
} }
#endif #endif
return 1; return 1;
} }
@ -865,8 +969,7 @@ namespace blt::gp
} }
template <typename Return, blt::size_t size, typename Accessor, blt::size_t... indexes> template <typename Return, blt::size_t size, typename Accessor, blt::size_t... indexes>
Return convert_array(std::array<blt::size_t, size>&& arr, Accessor&& accessor, Return convert_array(std::array<blt::size_t, size>&& arr, Accessor&& accessor, std::integer_sequence<blt::size_t, indexes...>)
std::integer_sequence<blt::size_t, indexes...>)
{ {
return Return{accessor(arr, indexes)...}; return Return{accessor(arr, indexes)...};
} }
@ -895,6 +998,8 @@ namespace blt::gp
double mutation_chance = 0; double mutation_chance = 0;
double reproduction_chance = 0; double reproduction_chance = 0;
std::optional<size_t> replacement_amount;
void update(const prog_config_t& config) void update(const prog_config_t& config)
{ {
const auto total = config.crossover_chance + config.mutation_chance + config.reproduction_chance; const auto total = config.crossover_chance + config.mutation_chance + config.reproduction_chance;
@ -928,8 +1033,7 @@ namespace blt::gp
blt::barrier barrier; blt::barrier barrier;
explicit concurrency_storage(blt::size_t threads): barrier(threads, lifetime_over) explicit concurrency_storage(blt::size_t threads): barrier(threads, lifetime_over)
{ {}
}
} thread_helper{config.threads == 0 ? std::thread::hardware_concurrency() : config.threads}; } thread_helper{config.threads == 0 ? std::thread::hardware_concurrency() : config.threads};
std::unique_ptr<std::function<void(blt::size_t)>> thread_execution_service = nullptr; std::unique_ptr<std::function<void(blt::size_t)>> thread_execution_service = nullptr;