diff --git a/CMakeLists.txt b/CMakeLists.txt index fa56855..65a80e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(blt-gp VERSION 0.1.35) +project(blt-gp VERSION 0.1.36) include(CTest) @@ -16,7 +16,7 @@ set(CMAKE_CXX_STANDARD 17) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -g") +#SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -g") if (NOT TARGET BLT) add_subdirectory(lib/blt) diff --git a/include/blt/gp/program.h b/include/blt/gp/program.h index fc4b134..ac0be48 100644 --- a/include/blt/gp/program.h +++ b/include/blt/gp/program.h @@ -362,8 +362,11 @@ namespace blt::gp #ifdef BLT_TRACK_ALLOCATIONS auto gen_alloc = blt::gp::tracker.start_measurement(); #endif + 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()); // should already be empty - next_pop.clear(); thread_helper.next_gen_left.store(config.population_size, std::memory_order_release); (*thread_execution_service)(0); #ifdef BLT_TRACK_ALLOCATIONS @@ -375,8 +378,10 @@ namespace blt::gp void next_generation() { + 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, - ("pop size: " + std::to_string(next_pop.get_individuals().size())).c_str()); + ("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str()); std::swap(current_pop, next_pop); current_generation++; } @@ -400,6 +405,7 @@ namespace blt::gp current_generation = 0; 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); if (eval_fitness_now) evaluate_fitness_internal(); } @@ -465,21 +471,24 @@ namespace blt::gp } if (thread_helper.next_gen_left > 0) { - static thread_local tracked_vector new_children; - new_children.clear(); - auto args = get_selector_args(new_children); + auto args = get_selector_args(); crossover_selection.pre_process(*this, current_pop); mutation_selection.pre_process(*this, current_pop); reproduction_selection.pre_process(*this, current_pop); - perform_elitism(args); + perform_elitism(args, next_pop); - while (new_children.size() < config.population_size) - func(args, crossover_selection, mutation_selection, reproduction_selection); + blt::size_t start = config.elites; - for (auto& i : new_children) - next_pop.get_individuals().emplace_back(std::move(i)); + while (start < config.population_size) + { + tree_t& c1 = next_pop.get_individuals()[start].tree; + 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); + } thread_helper.next_gen_left = 0; } @@ -542,9 +551,7 @@ namespace blt::gp } if (thread_helper.next_gen_left > 0) { - static thread_local tracked_vector new_children; - new_children.clear(); - auto args = get_selector_args(new_children); + auto args = get_selector_args(); if (id == 0) { current_stats.normalized_fitness.clear(); @@ -562,37 +569,35 @@ namespace blt::gp if (&crossover_selection != &reproduction_selection) reproduction_selection.pre_process(*this, current_pop); - perform_elitism(args); + perform_elitism(args, next_pop); - for (auto& i : new_children) - next_pop.get_individuals().emplace_back(std::move(i)); - thread_helper.next_gen_left -= new_children.size(); - new_children.clear(); + thread_helper.next_gen_left -= config.elites; } thread_helper.barrier.wait(); while (thread_helper.next_gen_left > 0) { blt::size_t size = 0; + blt::size_t begin = 0; blt::size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed); do { size = std::min(end, config.evaluation_size); + begin = end - 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 (new_children.size() < size) - func(args, crossover_selection, mutation_selection, reproduction_selection); - + while (begin != end) { - std::scoped_lock lock(thread_helper.thread_generation_lock); - for (auto& i : new_children) - { - if (next_pop.get_individuals().size() < config.population_size) - next_pop.get_individuals().emplace_back(i); - } + auto index = config.elites + begin; + tree_t& c1 = next_pop.get_individuals()[index].tree; + tree_t* c2 = nullptr; + if (index + 1 < end) + c2 = &next_pop.get_individuals()[index + 1].tree; + begin += func(args, crossover_selection, mutation_selection, reproduction_selection, c1, c2); } + } } thread_helper.barrier.wait(); @@ -747,9 +752,9 @@ namespace blt::gp } private: - inline selector_args get_selector_args(tracked_vector& next_pop_trees) + inline selector_args get_selector_args() { - return {*this, next_pop_trees, current_pop, current_stats, config, get_random()}; + return {*this, current_pop, current_stats, config, get_random()}; } template @@ -765,7 +770,7 @@ namespace blt::gp { statistic_history.push_back(current_stats); current_stats.clear(); - thread_helper.evaluation_left.store(current_pop.get_individuals().size(), std::memory_order_release); + thread_helper.evaluation_left.store(config.population_size, std::memory_order_release); (*thread_execution_service)(0); current_stats.average_fitness = current_stats.overall_fitness / static_cast(config.population_size); @@ -791,7 +796,6 @@ namespace blt::gp std::vector> threads; std::mutex thread_function_control{}; - std::mutex thread_generation_lock{}; std::condition_variable thread_function_condition{}; std::atomic_uint64_t evaluation_left = 0; diff --git a/include/blt/gp/selection.h b/include/blt/gp/selection.h index c2b83b8..a3c3856 100644 --- a/include/blt/gp/selection.h +++ b/include/blt/gp/selection.h @@ -32,15 +32,14 @@ namespace blt::gp struct selector_args { gp_program& program; - tracked_vector& next_pop; - population_t& current_pop; + const population_t& current_pop; population_stats& current_stats; prog_config_t& config; random_t& random; }; - constexpr inline auto perform_elitism = [](const selector_args& args) { - auto& [program, next_pop, current_pop, current_stats, config, random] = args; + constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop) { + auto& [program, current_pop, current_stats, config, random] = args; if (config.elites > 0) { @@ -70,39 +69,38 @@ namespace blt::gp } for (blt::size_t i = 0; i < config.elites; i++) - next_pop.push_back(current_pop.get_individuals()[values[i].first].tree); + next_pop.get_individuals()[i].copy_fast(current_pop.get_individuals()[values[i].first].tree); } }; template constexpr inline auto default_next_pop_creator = []( - blt::gp::selector_args& args, Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection) { - auto& [program, next_pop, current_pop, current_stats, config, random] = args; + blt::gp::selector_args& args, Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection, + tree_t& c1, tree_t* c2) { + auto& [program, current_pop, current_stats, config, random] = args; int sel = random.get_i32(0, 3); switch (sel) { case 0: + if (c2 == nullptr) + return 0; // everyone gets a chance once per loop. if (random.choice(config.crossover_chance)) { // auto state = tracker.start_measurement(); // crossover - auto& p1 = crossover_selection.select(program, current_pop); - auto& p2 = crossover_selection.select(program, current_pop); - - auto results = config.crossover.get().apply(program, p1, p2); - - // if crossover fails, we can check for mutation on these guys. otherwise straight copy them into the next pop - if (results) + const tree_t* p1; + const tree_t* p2; + do { - next_pop.push_back(std::move(results->child1)); - if (next_pop.size() != config.population_size) - next_pop.push_back(std::move(results->child2)); - } + p1 = &crossover_selection.select(program, current_pop); + p2 = &crossover_selection.select(program, current_pop); + } while (!config.crossover.get().apply(program, *p1, *p2, c1, *c2)); // tracker.stop_measurement(state); // BLT_TRACE("Crossover Allocated %ld times with a total of %s", state.getAllocationDifference(), // blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str()); + return 2; } break; case 1: @@ -110,11 +108,15 @@ namespace blt::gp { // auto state = tracker.start_measurement(); // mutation - auto& p = mutation_selection.select(program, current_pop); - next_pop.push_back(std::move(config.mutator.get().apply(program, p))); + const tree_t* p; + do + { + p = &mutation_selection.select(program, current_pop); + } while (!config.mutator.get().apply(program, *p, c1)); // tracker.stop_measurement(state); // BLT_TRACE("Mutation Allocated %ld times with a total of %s", state.getAllocationDifference(), // blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str()); + return 1; } break; case 2: @@ -122,11 +124,11 @@ namespace blt::gp { // auto state = tracker.start_measurement(); // reproduction - auto& p = reproduction_selection.select(program, current_pop); - next_pop.push_back(p); + c1 = reproduction_selection.select(program, current_pop); // tracker.stop_measurement(state); // BLT_TRACE("Reproduction Allocated %ld times with a total of %s", state.getAllocationDifference(), // blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str()); + return 1; } break; default: @@ -136,6 +138,7 @@ namespace blt::gp BLT_UNREACHABLE; #endif } + return 0; }; class selection_t @@ -147,7 +150,7 @@ namespace blt::gp * @param stats the populations statistics * @return */ - virtual tree_t& select(gp_program& program, population_t& pop) = 0; + virtual const tree_t& select(gp_program& program, const population_t& pop) = 0; virtual void pre_process(gp_program&, population_t&) {} @@ -158,19 +161,19 @@ namespace blt::gp class select_best_t : public selection_t { public: - tree_t& select(gp_program& program, population_t& pop) final; + const tree_t& select(gp_program& program, const population_t& pop) final; }; class select_worst_t : public selection_t { public: - tree_t& select(gp_program& program, population_t& pop) final; + const tree_t& select(gp_program& program, const population_t& pop) final; }; class select_random_t : public selection_t { public: - tree_t& select(gp_program& program, population_t& pop) final; + const tree_t& select(gp_program& program, const population_t& pop) final; }; class select_tournament_t : public selection_t @@ -182,7 +185,7 @@ namespace blt::gp BLT_ABORT("Unable to select with this size. Must select at least 1 individual_t!"); } - tree_t& select(gp_program& program, population_t& pop) final; + const tree_t& select(gp_program& program, const population_t& pop) final; private: const blt::size_t selection_size; @@ -191,7 +194,7 @@ namespace blt::gp class select_fitness_proportionate_t : public selection_t { public: - tree_t& select(gp_program& program, population_t& pop) final; + const tree_t& select(gp_program& program, const population_t& pop) final; }; } diff --git a/include/blt/gp/transformers.h b/include/blt/gp/transformers.h index e1a229d..15c5765 100644 --- a/include/blt/gp/transformers.h +++ b/include/blt/gp/transformers.h @@ -57,16 +57,6 @@ namespace blt::gp class crossover_t { public: - enum class error_t - { - NO_VALID_TYPE, - TREE_TOO_SMALL - }; - struct result_t - { - tree_t child1; - tree_t child2; - }; struct crossover_point_t { blt::ptrdiff_t p1_crossover_point; @@ -87,7 +77,7 @@ namespace blt::gp explicit crossover_t(const config_t& config): config(config) {} - blt::expected get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const; + std::optional get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const; /** * child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover. @@ -97,7 +87,7 @@ namespace blt::gp * @param p2 reference to the second parent * @return expected pair of child otherwise returns error enum */ - virtual blt::expected apply(gp_program& program, const tree_t& p1, const tree_t& p2); // NOLINT + virtual bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2); // NOLINT virtual ~crossover_t() = default; @@ -126,7 +116,7 @@ namespace blt::gp explicit mutation_t(const config_t& config): config(config) {} - virtual tree_t apply(gp_program& program, const tree_t& p); + virtual bool apply(gp_program& program, const tree_t& p, tree_t& c); // returns the point after the mutation blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node); @@ -155,7 +145,7 @@ namespace blt::gp explicit advanced_mutation_t(const config_t& config): mutation_t(config) {} - tree_t apply(gp_program& program, const tree_t& p) final; + bool apply(gp_program& program, const tree_t& p, tree_t& c) final; advanced_mutation_t& set_per_node_mutation_chance(double v) { diff --git a/include/blt/gp/tree.h b/include/blt/gp/tree.h index 7663957..51d833f 100644 --- a/include/blt/gp/tree.h +++ b/include/blt/gp/tree.h @@ -56,6 +56,37 @@ namespace blt::gp public: explicit tree_t(gp_program& program); + tree_t(const tree_t& copy) = default; + + tree_t& operator=(const tree_t& copy) + { + if (this == ©) + return *this; + copy_fast(copy); + return *this; + } + + /** + * This function copies the data from the provided tree, will attempt to reserve and copy in one step. + * will avoid reallocation if enough space is already present. + */ + void copy_fast(const tree_t& copy) + { + if (this == ©) + return; + values.reserve(copy.values.internal_storage_size()); + values.reset(); + values.insert(copy.values); + + operations.clear(); + operations.reserve(copy.operations.size()); + operations.insert(operations.begin(), copy.operations.begin(), copy.operations.end()); + } + + tree_t(tree_t&& move) = default; + + tree_t& operator=(tree_t&& move) = default; + void clear(gp_program& program); struct child_t @@ -178,6 +209,14 @@ namespace blt::gp tree_t tree; fitness_t fitness; + void copy_fast(const tree_t& copy) + { + // fast copy of the tree + tree.copy_fast(copy); + // reset fitness + fitness = {}; + } + individual_t() = delete; explicit individual_t(tree_t&& tree): tree(std::move(tree)) @@ -284,6 +323,11 @@ namespace blt::gp return individuals; } + [[nodiscard]] const tracked_vector& get_individuals() const + { + return individuals; + } + population_tree_iterator for_each_tree() { return population_tree_iterator{individuals, 0}; diff --git a/src/selection.cpp b/src/selection.cpp index 0bdb7f5..a81983c 100644 --- a/src/selection.cpp +++ b/src/selection.cpp @@ -21,11 +21,11 @@ namespace blt::gp { - tree_t& select_best_t::select(gp_program&, population_t& pop) + const tree_t& select_best_t::select(gp_program&, const population_t& pop) { auto& first = pop.get_individuals()[0]; double best_fitness = first.fitness.adjusted_fitness; - tree_t* tree = &first.tree; + const tree_t* tree = &first.tree; for (auto& ind : pop.get_individuals()) { if (ind.fitness.adjusted_fitness > best_fitness) @@ -37,11 +37,11 @@ namespace blt::gp return *tree; } - tree_t& select_worst_t::select(gp_program&, population_t& pop) + const tree_t& select_worst_t::select(gp_program&, const population_t& pop) { auto& first = pop.get_individuals()[0]; double worst_fitness = first.fitness.adjusted_fitness; - tree_t* tree = &first.tree; + const tree_t* tree = &first.tree; for (auto& ind : pop.get_individuals()) { if (ind.fitness.adjusted_fitness < worst_fitness) @@ -53,12 +53,12 @@ namespace blt::gp return *tree; } - tree_t& select_random_t::select(gp_program& program, population_t& pop) + const tree_t& select_random_t::select(gp_program& program, const population_t& pop) { return pop.get_individuals()[program.get_random().get_size_t(0ul, pop.get_individuals().size())].tree; } - tree_t& select_tournament_t::select(gp_program& program, population_t& pop) + const tree_t& select_tournament_t::select(gp_program& program, const population_t& pop) { blt::u64 best = program.get_random().get_u64(0, pop.get_individuals().size()); auto& i_ref = pop.get_individuals(); @@ -71,7 +71,7 @@ namespace blt::gp return i_ref[best].tree; } - tree_t& select_fitness_proportionate_t::select(gp_program& program, population_t& pop) + const tree_t& select_fitness_proportionate_t::select(gp_program& program, const population_t& pop) { auto& stats = program.get_population_stats(); auto choice = program.get_random().get_double(); diff --git a/src/transformers.cpp b/src/transformers.cpp index 154e2cf..9e0d355 100644 --- a/src/transformers.cpp +++ b/src/transformers.cpp @@ -59,23 +59,21 @@ namespace blt::gp mutation_t::config_t::config_t(): generator(grow_generator) {} - blt::expected crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2) // NOLINT + bool crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) // NOLINT { - result_t result{p1, p2}; - - auto& c1 = result.child1; - auto& c2 = result.child2; + c1.copy_fast(p1); + c2.copy_fast(p2); auto& c1_ops = c1.get_operations(); auto& c2_ops = c2.get_operations(); if (c1_ops.size() < 5 || c2_ops.size() < 5) - return blt::unexpected(error_t::TREE_TOO_SMALL); + return false; - auto point = get_crossover_point(program, c1, c2); + auto point = get_crossover_point(program, p1, p2); if (!point) - return blt::unexpected(point.error()); + return false; auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point; auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point); @@ -104,8 +102,8 @@ namespace blt::gp blt::size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr); auto c1_total = static_cast(c1_stack_after_bytes + c1_stack_for_bytes); auto c2_total = static_cast(c2_stack_after_bytes + c2_stack_for_bytes); - auto copy_ptr_c1 = get_thread_pointer_for_size(c1_total); - auto copy_ptr_c2 = get_thread_pointer_for_size(c2_total); + auto copy_ptr_c1 = get_thread_pointer_for_size(c1_total); + auto copy_ptr_c2 = get_thread_pointer_for_size(c2_total); c1_stack.copy_to(copy_ptr_c1, c1_total); c1_stack.pop_bytes(c1_total); @@ -131,8 +129,8 @@ namespace blt::gp c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end()); #if BLT_DEBUG_LEVEL >= 2 - blt::size_t c1_found_bytes = result.child1.get_values().size().total_used_bytes; - blt::size_t c2_found_bytes = result.child2.get_values().size().total_used_bytes; + blt::size_t c1_found_bytes = c1.get_values().size().total_used_bytes; + blt::size_t c2_found_bytes = c2.get_values().size().total_used_bytes; blt::size_t c1_expected_bytes = std::accumulate(result.child1.get_operations().begin(), result.child1.get_operations().end(), 0ul, [](const auto& v1, const auto& v2) { if (v2.is_value) @@ -153,10 +151,10 @@ namespace blt::gp } #endif - return result; + return true; } - blt::expected crossover_t::get_crossover_point(gp_program& program, const tree_t& c1, + std::optional crossover_t::get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const { auto& c1_ops = c1.get_operations(); @@ -194,10 +192,10 @@ namespace blt::gp } } if (!found) - return blt::unexpected(error_t::NO_VALID_TYPE); + return {}; } // should we try again over the whole tree? probably not. - return blt::unexpected(error_t::NO_VALID_TYPE); + return {}; } else { attempted_point = program.get_random().get_size_t(1ul, c2_ops.size()); @@ -213,13 +211,13 @@ namespace blt::gp return crossover_point_t{static_cast(crossover_point), static_cast(attempted_point)}; } - tree_t mutation_t::apply(gp_program& program, const tree_t& p) + bool mutation_t::apply(gp_program& program, const tree_t& p, tree_t& c) { - auto c = p; + c.copy_fast(p); mutate_point(program, c, program.get_random().get_size_t(0ul, c.get_operations().size())); - return c; + return true; } blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node) @@ -304,10 +302,10 @@ namespace blt::gp return begin_point + new_ops_r.size(); } - tree_t advanced_mutation_t::apply(gp_program& program, const tree_t& p) + bool advanced_mutation_t::apply(gp_program& program, const tree_t& p, tree_t& c) { // child tree - tree_t c = p; + c.copy_fast(p); auto& ops = c.get_operations(); auto& vals = c.get_values(); @@ -732,6 +730,6 @@ namespace blt::gp } #endif - return c; + return true; } } \ No newline at end of file