dev-func-drop
Brett 2025-01-21 17:29:22 -05:00
parent f83ad3b2f4
commit b79fc5a0f1
4 changed files with 165 additions and 184 deletions

View File

@ -131,7 +131,7 @@ namespace blt::gp::example
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", record.get_hits(), record.get_total(), record.get_percent_hit());
std::cout << record.pretty_print() << std::endl;
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(program, std::cout);
i.tree.print(std::cout);
std::cout << "\n";
}

View File

@ -52,9 +52,9 @@ namespace blt::gp
std::reference_wrapper<crossover_t> crossover;
std::reference_wrapper<population_initializer_t> pop_initializer;
blt::size_t threads = std::thread::hardware_concurrency();
size_t threads = std::thread::hardware_concurrency();
// number of elements each thread should pull per execution. this is for granularity performance and can be optimized for better results!
blt::size_t evaluation_size = 4;
size_t evaluation_size = 4;
// default config (ramped half-and-half init) or for buildering
prog_config_t();

View File

@ -297,11 +297,13 @@ namespace blt::gp
explicit gp_program(blt::u64 seed): seed_func([seed] { return seed; })
{
create_threads();
selection_probabilities.update(config);
}
explicit gp_program(blt::u64 seed, const prog_config_t& config): seed_func([seed] { return seed; }), config(config)
{
create_threads();
selection_probabilities.update(config);
}
/**
@ -312,11 +314,13 @@ namespace blt::gp
explicit gp_program(std::function<blt::u64()> seed_func): seed_func(std::move(seed_func))
{
create_threads();
selection_probabilities.update(config);
}
explicit gp_program(std::function<blt::u64()> seed_func, const prog_config_t& config): seed_func(std::move(seed_func)), config(config)
{
create_threads();
selection_probabilities.update(config);
}
~gp_program()
@ -398,11 +402,10 @@ namespace blt::gp
*
* NOTE: 0 is considered the best, in terms of standardized fitness
*/
template <typename FitnessFunc, typename Crossover, typename Mutation, typename Reproduction, typename CreationFunc = decltype(
default_next_pop_creator<Crossover, Mutation, Reproduction>)>
template <typename FitnessFunc, typename Crossover, typename Mutation, typename Reproduction>
void generate_population(type_id root_type, FitnessFunc& fitness_function,
Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
CreationFunc& func = default_next_pop_creator<Crossover, Mutation, Reproduction>, 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(
@ -415,8 +418,8 @@ namespace blt::gp
if (config.threads == 1)
{
BLT_INFO("Starting with single thread variant!");
thread_execution_service = std::unique_ptr<std::function<void(blt::size_t)>>(new std::function(
[this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection, &func](blt::size_t)
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)
{
if (thread_helper.evaluation_left > 0)
{
@ -427,8 +430,7 @@ namespace blt::gp
ind.fitness = {};
if constexpr (std::is_same_v<LambdaReturn, bool> || std::is_convertible_v<LambdaReturn, bool>)
{
auto result = fitness_function(ind.tree, ind.fitness, index);
if (result)
if (fitness_function(ind.tree, ind.fitness, index))
fitness_should_exit = true;
}
else
@ -458,7 +460,7 @@ namespace blt::gp
mutation_selection.pre_process(*this, current_pop);
reproduction_selection.pre_process(*this, current_pop);
blt::size_t start = perform_elitism(args, next_pop);
size_t start = detail::perform_elitism(args, next_pop);
while (start < config.population_size)
{
@ -466,7 +468,7 @@ namespace blt::gp
tree_t* c2 = nullptr;
if (start + 1 < config.population_size)
c2 = &next_pop.get_individuals()[start + 1].tree;
start += func(args, crossover_selection, mutation_selection, reproduction_selection, c1, c2, fitness_function);
start += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
}
thread_helper.next_gen_left = 0;
@ -478,7 +480,7 @@ namespace blt::gp
BLT_INFO("Starting thread execution service!");
std::scoped_lock lock(thread_helper.thread_function_control);
thread_execution_service = std::unique_ptr<std::function<void(blt::size_t)>>(new std::function(
[this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection, &func](size_t id)
[this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t id)
{
thread_helper.barrier.wait();
@ -559,7 +561,7 @@ namespace blt::gp
mutation_selection.pre_process(*this, current_pop);
if (&crossover_selection != &reproduction_selection)
reproduction_selection.pre_process(*this, current_pop);
const auto elite_amount = perform_elitism(args, next_pop);
const auto elite_amount = detail::perform_elitism(args, next_pop);
thread_helper.next_gen_left -= elite_amount;
}
thread_helper.barrier.wait();
@ -585,7 +587,7 @@ namespace blt::gp
tree_t* c2 = nullptr;
if (begin + 1 < end)
c2 = &next_pop.get_individuals()[index + 1].tree;
begin += func(args, crossover_selection, mutation_selection, reproduction_selection, c1, c2, fitness_function);
begin += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
}
}
}
@ -639,6 +641,11 @@ namespace blt::gp
[[nodiscard]] random_t& get_random() const;
[[nodiscard]] const prog_config_t& get_config() const
{
return config;
}
[[nodiscard]] type_provider& get_typesystem()
{
return storage.system;
@ -754,6 +761,101 @@ namespace blt::gp
}
private:
template <typename Crossover, typename Mutation, typename Reproduction>
size_t perform_selection(Crossover& crossover, Mutation& mutation, Reproduction& reproduction, tree_t& c1, tree_t* c2)
{
if (get_random().choice(selection_probabilities.crossover_chance))
{
thread_local tree_t tree{*this};
tree.clear(*this);
auto ptr = c2;
if (ptr == nullptr)
ptr = &tree;
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
const tree_t* p1;
const tree_t* p2;
size_t runs = 0;
// double parent_val = 0;
do
{
p1 = &crossover.select(*this, current_pop);
p2 = &crossover.select(*this, current_pop);
c1.copy_fast(*p1);
ptr->copy_fast(*p2);
if (++runs >= config.crossover.get().get_config().max_crossover_iterations)
return 0;
#ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.value(1);
#endif
}
while (!config.crossover.get().apply(*this, *p1, *p2, c1, *ptr));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
crossover_calls.call();
if (state.getAllocatedByteDifference() != 0)
{
crossover_allocations.call(state.getAllocatedByteDifference());
crossover_allocations.set_value(std::max(crossover_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
if (c2 == nullptr)
tree.clear(*this);
return 2;
}
if (get_random().choice(selection_probabilities.mutation_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// mutation
const tree_t* p;
do
{
p = &mutation.select(*this, current_pop);
c1.copy_fast(*p);
#ifdef BLT_TRACK_ALLOCATIONS
mutation_calls.value(1);
#endif
}
while (!config.mutator.get().apply(*this, *p, c1));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
mutation_calls.call();
if (state.getAllocationDifference() != 0)
{
mutation_allocations.call(state.getAllocatedByteDifference());
mutation_allocations.set_value(std::max(mutation_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
return 1;
}
if (selection_probabilities.reproduction_chance > 0)
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// reproduction
c1.copy_fast(reproduction.select(*this, current_pop));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
reproduction_calls.call();
reproduction_calls.value(1);
if (state.getAllocationDifference() != 0)
{
reproduction_allocations.call(state.getAllocatedByteDifference());
reproduction_allocations.set_value(std::max(reproduction_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
return 1;
}
return 0;
}
selector_args get_selector_args()
{
return {*this, current_pop, current_stats, config, get_random()};
@ -783,6 +885,22 @@ namespace blt::gp
std::function<u64()> seed_func;
prog_config_t config{};
// internal cache which stores already calculated probability values
struct
{
double crossover_chance = 0;
double mutation_chance = 0;
double reproduction_chance = 0;
void update(const prog_config_t& config)
{
const auto total = config.crossover_chance + config.mutation_chance + config.reproduction_chance;
crossover_chance = config.crossover_chance / total;
mutation_chance = config.mutation_chance / total;
reproduction_chance = config.reproduction_chance / total;
}
} selection_probabilities;
population_t current_pop;
population_t next_pop;

View File

@ -39,188 +39,51 @@ namespace blt::gp
random_t& random;
};
constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop)
namespace detail
{
auto& [program, current_pop, current_stats, config, random] = args;
BLT_ASSERT_MSG(config.elites <= current_pop.get_individuals().size(), ("Not enough individuals in population (" +
std::to_string(current_pop.get_individuals().size()) +
") for requested amount of elites (" + std::to_string(config.elites) + ")").c_str());
if (config.elites > 0 && current_pop.get_individuals().size() >= config.elites)
constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop)
{
static thread_local tracked_vector<std::pair<std::size_t, double>> values;
values.clear();
auto& [program, current_pop, current_stats, config, random] = args;
for (blt::size_t i = 0; i < config.elites; i++)
values.emplace_back(i, current_pop.get_individuals()[i].fitness.adjusted_fitness);
BLT_ASSERT_MSG(config.elites <= current_pop.get_individuals().size(), ("Not enough individuals in population (" +
std::to_string(current_pop.get_individuals().size()) +
") for requested amount of elites (" + std::to_string(config.elites) + ")").c_str());
for (const auto& ind : blt::enumerate(current_pop.get_individuals()))
if (config.elites > 0 && current_pop.get_individuals().size() >= config.elites)
{
for (blt::size_t i = 0; i < config.elites; i++)
thread_local tracked_vector<std::pair<std::size_t, double>> values;
values.clear();
for (size_t i = 0; i < config.elites; i++)
values.emplace_back(i, current_pop.get_individuals()[i].fitness.adjusted_fitness);
for (const auto& ind : blt::enumerate(current_pop.get_individuals()))
{
if (ind.value.fitness.adjusted_fitness >= values[i].second)
for (size_t i = 0; i < config.elites; i++)
{
bool doesnt_contain = true;
for (blt::size_t j = 0; j < config.elites; j++)
if (ind.value.fitness.adjusted_fitness >= values[i].second)
{
if (ind.index == values[j].first)
doesnt_contain = false;
bool doesnt_contain = true;
for (blt::size_t j = 0; j < config.elites; j++)
{
if (ind.index == values[j].first)
doesnt_contain = false;
}
if (doesnt_contain)
values[i] = {ind.index, ind.value.fitness.adjusted_fitness};
break;
}
if (doesnt_contain)
values[i] = {ind.index, ind.value.fitness.adjusted_fitness};
break;
}
}
for (size_t i = 0; i < config.elites; i++)
next_pop.get_individuals()[i].copy_fast(current_pop.get_individuals()[values[i].first].tree);
return config.elites;
}
return 0ul;
};
for (blt::size_t i = 0; i < config.elites; i++)
next_pop.get_individuals()[i].copy_fast(current_pop.get_individuals()[values[i].first].tree);
return config.elites;
}
return 0ul;
};
inline std::atomic<double> parent_fitness = 0;
inline std::atomic<double> child_fitness = 0;
template <typename Crossover, typename Mutation, typename Reproduction>
constexpr inline auto default_next_pop_creator = [](
selector_args& args, Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
tree_t& c1, tree_t* c2, const std::function<bool(const tree_t&, fitness_t&, size_t)>& test_fitness_func)
{
auto& [program, current_pop, current_stats, config, random] = args;
switch (random.get_i32(0, 3))
{
case 0:
if (c2 == nullptr)
return 0;
// everyone gets a chance once per loop.
if (random.choice(config.crossover_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// crossover
const tree_t* p1;
const tree_t* p2;
size_t runs = 0;
// double parent_val = 0;
do
{
p1 = &crossover_selection.select(program, current_pop);
p2 = &crossover_selection.select(program, current_pop);
// fitness_t fitness1;
// fitness_t fitness2;
// test_fitness_func(*p1, fitness1, 0);
// test_fitness_func(*p2, fitness2, 0);
// parent_val = fitness1.adjusted_fitness + fitness2.adjusted_fitness;
// BLT_TRACE("%ld> P1 Fit: %lf, P2 Fit: %lf", val, fitness1.adjusted_fitness, fitness2.adjusted_fitness);
c1.copy_fast(*p1);
c2->copy_fast(*p2);
if (++runs >= config.crossover.get().get_config().max_crossover_iterations)
return 0;
#ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.value(1);
#endif
}
while (!config.crossover.get().apply(program, *p1, *p2, c1, *c2));
// fitness_t fitness1;
// fitness_t fitness2;
// test_fitness_func(c1, fitness1, 0);
// test_fitness_func(*c2, fitness2, 0);
// const auto child_val = fitness1.adjusted_fitness + fitness2.adjusted_fitness;
// auto old_parent_val = parent_fitness.load(std::memory_order_relaxed);
// while (!parent_fitness.compare_exchange_weak(old_parent_val, old_parent_val + parent_val, std::memory_order_relaxed,
// std::memory_order_relaxed))
// {
// }
// auto old_child_val = child_fitness.load(std::memory_order_relaxed);
// while (!child_fitness.compare_exchange_weak(old_child_val, old_child_val + child_val, std::memory_order_relaxed,
// std::memory_order_relaxed))
// {
// }
// BLT_TRACE("%ld> C1 Fit: %lf, C2 Fit: %lf", val, fitness1.adjusted_fitness, fitness2.adjusted_fitness);
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
crossover_calls.call();
if (state.getAllocatedByteDifference() != 0)
{
crossover_allocations.call(state.getAllocatedByteDifference());
crossover_allocations.set_value(std::max(crossover_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
return 2;
}
break;
case 1:
if (random.choice(config.mutation_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// mutation
const tree_t* p;
do
{
p = &mutation_selection.select(program, current_pop);
c1.copy_fast(*p);
#ifdef BLT_TRACK_ALLOCATIONS
mutation_calls.value(1);
#endif
}
while (!config.mutator.get().apply(program, *p, c1));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
mutation_calls.call();
if (state.getAllocationDifference() != 0)
{
mutation_allocations.call(state.getAllocatedByteDifference());
mutation_allocations.set_value(std::max(mutation_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
return 1;
}
break;
case 2:
if (config.reproduction_chance > 0 && random.choice(config.reproduction_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// reproduction
c1.copy_fast(reproduction_selection.select(program, current_pop));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
reproduction_calls.call();
reproduction_calls.value(1);
if (state.getAllocationDifference() != 0)
{
reproduction_allocations.call(state.getAllocatedByteDifference());
reproduction_allocations.set_value(std::max(reproduction_allocations.get_value(), state.getAllocatedByteDifference()));
}
#endif
return 1;
}
break;
default:
#if BLT_DEBUG_LEVEL > 0
BLT_ABORT("This is not possible!");
#else
BLT_UNREACHABLE;
#endif
}
return 0;
};
}
class selection_t
{