From fc7cd292af98600278cef26ebd31e3fb4110a97d Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Sat, 20 Jul 2024 13:02:41 -0400 Subject: [PATCH] cleanup crossover function --- CMakeLists.txt | 2 +- include/blt/gp/transformers.h | 18 ++- src/transformers.cpp | 227 ++++++++++++---------------------- 3 files changed, 97 insertions(+), 150 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 57b8949..5eeae87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(blt-gp VERSION 0.0.90) +project(blt-gp VERSION 0.0.91) include(CTest) diff --git a/include/blt/gp/transformers.h b/include/blt/gp/transformers.h index 217f8fc..f846ad3 100644 --- a/include/blt/gp/transformers.h +++ b/include/blt/gp/transformers.h @@ -31,6 +31,7 @@ namespace blt::gp class crossover_t { public: + using op_iter = std::vector::iterator; enum class error_t { NO_VALID_TYPE, @@ -41,6 +42,11 @@ namespace blt::gp tree_t child1; tree_t child2; }; + struct crossover_point_t + { + blt::ptrdiff_t p1_crossover_point; + blt::ptrdiff_t p2_crossover_point; + }; struct config_t { // number of times crossover will try to pick a valid point in the tree. this is purely based on the return type of the operators @@ -56,6 +62,14 @@ 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; + + static blt::ptrdiff_t find_endpoint(blt::gp::gp_program& program, const std::vector& container, + blt::ptrdiff_t start); + + static void transfer_forward(blt::gp::stack_allocator& from, blt::gp::stack_allocator& to, op_iter begin, op_iter end); + static void transfer_backward(blt::gp::stack_allocator& from, blt::gp::stack_allocator& to, op_iter begin, op_iter end); + /** * child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover. * the parents are not modified during this process @@ -68,7 +82,7 @@ namespace blt::gp virtual ~crossover_t() = default; - private: + protected: config_t config; }; @@ -97,7 +111,7 @@ namespace blt::gp virtual ~mutation_t() = default; - private: + protected: config_t config; }; diff --git a/src/transformers.cpp b/src/transformers.cpp index 956bb87..0819b36 100644 --- a/src/transformers.cpp +++ b/src/transformers.cpp @@ -27,11 +27,6 @@ namespace blt::gp blt::expected crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2) // NOLINT { result_t result{p1, p2}; - -#if BLT_DEBUG_LEVEL > 0 - BLT_INFO("Child 1 Stack empty? %s", result.child1.get_values().empty() ? "true" : "false"); - BLT_INFO("Child 2 Stack empty? %s", result.child2.get_values().empty() ? "true" : "false"); -#endif auto& c1 = result.child1; auto& c2 = result.child2; @@ -42,6 +37,67 @@ namespace blt::gp if (c1_ops.size() < 5 || c2_ops.size() < 5) return blt::unexpected(error_t::TREE_TOO_SMALL); + auto point = get_crossover_point(program, c1, c2); + + if (!point) + return blt::unexpected(point.error()); + + auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point; + auto crossover_point_end_itr = c1_ops.begin() + find_endpoint(program, c1_ops, point->p1_crossover_point); + + auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point; + auto found_point_end_itr = c2_ops.begin() + find_endpoint(program, c2_ops, point->p2_crossover_point); + + stack_allocator& c1_stack_init = c1.get_values(); + stack_allocator& c2_stack_init = c2.get_values(); + + std::vector c1_operators; + std::vector c2_operators; + + for (const auto& op : blt::enumerate(crossover_point_begin_itr, crossover_point_end_itr)) + c1_operators.push_back(op); + for (const auto& op : blt::enumerate(found_point_begin_itr, found_point_end_itr)) + c2_operators.push_back(op); + + stack_allocator c1_stack_after_copy; + stack_allocator c1_stack_for_copy; + stack_allocator c2_stack_after_copy; + stack_allocator c2_stack_for_copy; + + // transfer all values after the crossover point. these will need to be transferred back to child2 + transfer_backward(c1_stack_init, c1_stack_after_copy, c1_ops.end()-1, crossover_point_end_itr - 1); + // transfer all values for the crossover point. + transfer_backward(c1_stack_init, c1_stack_for_copy, crossover_point_end_itr - 1, crossover_point_begin_itr - 1); + // transfer child2 values for copying back into c1 + transfer_backward(c2_stack_init, c2_stack_after_copy, c2_ops.end() - 1, found_point_end_itr - 1); + transfer_backward(c2_stack_init, c2_stack_for_copy, found_point_end_itr - 1, found_point_begin_itr - 1); + // now copy back into the respective children + transfer_forward(c2_stack_for_copy, c1.get_values(), found_point_begin_itr, found_point_end_itr); + transfer_forward(c1_stack_for_copy, c2.get_values(), crossover_point_begin_itr, crossover_point_end_itr); + // now copy after the crossover point back to the correct children + transfer_forward(c1_stack_after_copy, c1.get_values(), crossover_point_end_itr, c1_ops.end()); + transfer_forward(c2_stack_after_copy, c2.get_values(), found_point_end_itr, c2_ops.end()); + + // now swap the operators + auto insert_point_c1 = crossover_point_begin_itr - 1; + auto insert_point_c2 = found_point_begin_itr - 1; + + // invalidates [begin, end()) so the insert points should be fine + c1_ops.erase(crossover_point_begin_itr, crossover_point_end_itr); + c2_ops.erase(found_point_begin_itr, found_point_end_itr); + + c1_ops.insert(++insert_point_c1, c2_operators.begin(), c2_operators.end()); + c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end()); + + return result; + } + + blt::expected crossover_t::get_crossover_point(gp_program& program, const tree_t& c1, + const tree_t& c2) const + { + auto& c1_ops = c1.get_operations(); + auto& c2_ops = c2.get_operations(); + blt::size_t crossover_point = program.get_random().get_size_t(1ul, c1_ops.size()); while (config.avoid_terminals && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal()) @@ -90,17 +146,16 @@ namespace blt::gp } } while (true); + return crossover_point_t{static_cast(crossover_point), static_cast(attempted_point)}; + } + + blt::ptrdiff_t crossover_t::find_endpoint(blt::gp::gp_program& program, const std::vector& container, blt::ptrdiff_t index) + { blt::i64 children_left = 0; - blt::size_t index = crossover_point; do { - const auto& type = program.get_operator_info(c1_ops[index].id); -#if BLT_DEBUG_LEVEL > 1 - #define MAKE_C_STR() program.get_name(c1_ops[index].id).has_value() ? std::string(program.get_name(c1_ops[index].id).value()).c_str() : std::to_string(c1_ops[index].id).c_str() - BLT_TRACE("Crossover type: %s, op: %s", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), MAKE_C_STR()); - #undef MAKE_C_STR -#endif + const auto& type = program.get_operator_info(container[index].id); // this is a child to someone if (children_left != 0) children_left--; @@ -109,148 +164,26 @@ namespace blt::gp index++; } while (children_left > 0); - auto crossover_point_begin_itr = c1_ops.begin() + static_cast(crossover_point); - auto crossover_point_end_itr = c1_ops.begin() + static_cast(index); -#if BLT_DEBUG_LEVEL > 0 - BLT_TRACE("[%ld %ld) %ld", crossover_point, index, index - crossover_point); -#endif - - children_left = 0; - index = attempted_point; - - do - { - const auto& type = program.get_operator_info(c2_ops[index].id); -#if BLT_DEBUG_LEVEL > 1 - #define MAKE_C_STR() program.get_name(c2_ops[index].id).has_value() ? std::string(program.get_name(c2_ops[index].id).value()).c_str() : std::to_string(c2_ops[index].id).c_str() - BLT_TRACE("Found type: %s, op: %s", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), MAKE_C_STR()); - #undef MAKE_C_STR -#endif - // this is a child to someone - if (children_left != 0) - children_left--; - if (type.argc.argc > 0) - children_left += type.argc.argc; - - index++; - } while (children_left > 0); - - auto found_point_begin_itr = c2_ops.begin() + static_cast(attempted_point); - auto found_point_end_itr = c2_ops.begin() + static_cast(index); - -#if BLT_DEBUG_LEVEL > 0 - BLT_TRACE("[%ld %ld) %ld", attempted_point, index, index - attempted_point); -#endif - - stack_allocator& c1_stack_init = c1.get_values(); - stack_allocator& c2_stack_init = c2.get_values(); - - std::vector c1_operators; - std::vector c2_operators; - - for (const auto& op : blt::enumerate(crossover_point_begin_itr, crossover_point_end_itr)) - c1_operators.push_back(op); - for (const auto& op : blt::enumerate(found_point_begin_itr, found_point_end_itr)) - c2_operators.push_back(op); - -#if BLT_DEBUG_LEVEL > 0 - BLT_TRACE("Sizes: %ld %ld || Ops size: %ld %ld", c1_operators.size(), c2_operators.size(), c1_ops.size(), c2_ops.size()); -#endif - - stack_allocator c1_stack_after_copy; - stack_allocator c1_stack_for_copy; - stack_allocator c2_stack_after_copy; - stack_allocator c2_stack_for_copy; - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring past crossover 1:"); -#endif - // transfer all values after the crossover point. these will need to be transferred back to child2 - for (auto it = c1_ops.end() - 1; it != crossover_point_end_itr - 1; it--) + return index; + } + + void crossover_t::transfer_backward(stack_allocator& from, stack_allocator& to, crossover_t::op_iter begin, crossover_t::op_iter end) + { + for (auto it = begin; it != end; it--) { if (it->is_value) - c1_stack_init.transfer_bytes(c1_stack_after_copy, it->type_size); + from.transfer_bytes(to, it->type_size); } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring for crossover 1:"); -#endif - // transfer all values for the crossover point. - for (auto it = crossover_point_end_itr - 1; it != crossover_point_begin_itr - 1; it--) - { - if (it->is_value) - c1_stack_init.transfer_bytes(c1_stack_for_copy, it->type_size); - } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring past crossover 2:"); -#endif - // transfer child2 values for copying back into c1 - for (auto it = c2_ops.end() - 1; it != found_point_end_itr - 1; it--) - { - if (it->is_value) - c2_stack_init.transfer_bytes(c2_stack_after_copy, it->type_size); - } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring for crossover 2:"); -#endif - for (auto it = found_point_end_itr - 1; it != found_point_begin_itr - 1; it--) - { - if (it->is_value) - c2_stack_init.transfer_bytes(c2_stack_for_copy, it->type_size); - } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring back for crossover 1:"); -#endif + } + + void crossover_t::transfer_forward(stack_allocator& from, stack_allocator& to, crossover_t::op_iter begin, crossover_t::op_iter end) + { // now copy back into the respective children - for (auto it = found_point_begin_itr; it != found_point_end_itr; it++) + for (auto it = begin; it != end; it++) { if (it->is_value) - c2_stack_for_copy.transfer_bytes(c1.get_values(), it->type_size); + from.transfer_bytes(to, it->type_size); } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring back for crossover 2:"); -#endif - for (auto it = crossover_point_begin_itr; it != crossover_point_end_itr; it++) - { - if (it->is_value) - c1_stack_for_copy.transfer_bytes(c2.get_values(), it->type_size); - } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring back after crossover 1:"); -#endif - // now copy after the crossover point back to the correct children - for (auto it = crossover_point_end_itr; it != c1_ops.end(); it++) - { - if (it->is_value) - c1_stack_after_copy.transfer_bytes(c1.get_values(), it->type_size); - } - -#if BLT_DEBUG_LEVEL > 1 - BLT_DEBUG("Transferring back after crossover 2:"); -#endif - for (auto it = found_point_end_itr; it != c2_ops.end(); it++) - { - if (it->is_value) - c2_stack_after_copy.transfer_bytes(c2.get_values(), it->type_size); - } - - // now swap the operators - auto insert_point_c1 = crossover_point_begin_itr - 1; - auto insert_point_c2 = found_point_begin_itr - 1; - - // invalidates [begin, end()) so the insert points should be fine - c1_ops.erase(crossover_point_begin_itr, crossover_point_end_itr); - c2_ops.erase(found_point_begin_itr, found_point_end_itr); - - c1_ops.insert(++insert_point_c1, c2_operators.begin(), c2_operators.end()); - c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end()); - - return result; } tree_t mutation_t::apply(gp_program& program, const tree_t& p)