diff --git a/examples/rice_classification.h b/examples/rice_classification.h index 9bb7194..2b03156 100644 --- a/examples/rice_classification.h +++ b/examples/rice_classification.h @@ -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"; } diff --git a/include/blt/gp/config.h b/include/blt/gp/config.h index 22c4baa..53eed10 100644 --- a/include/blt/gp/config.h +++ b/include/blt/gp/config.h @@ -52,9 +52,9 @@ namespace blt::gp std::reference_wrapper crossover; std::reference_wrapper 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(); diff --git a/include/blt/gp/program.h b/include/blt/gp/program.h index 0e375fa..dc51e50 100644 --- a/include/blt/gp/program.h +++ b/include/blt/gp/program.h @@ -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 seed_func): seed_func(std::move(seed_func)) { create_threads(); + selection_probabilities.update(config); } explicit gp_program(std::function 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 )> + template 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, bool eval_fitness_now = true) + bool eval_fitness_now = true) { using LambdaReturn = std::invoke_result_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>(new std::function( - [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection, &func](blt::size_t) + thread_execution_service = std::unique_ptr>(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 || std::is_convertible_v) { - 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>(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 + 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 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; diff --git a/include/blt/gp/selection.h b/include/blt/gp/selection.h index 0c3b7f4..460535b 100644 --- a/include/blt/gp/selection.h +++ b/include/blt/gp/selection.h @@ -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> 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> 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 parent_fitness = 0; - inline std::atomic child_fitness = 0; - - template - 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& 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 {