/* * <Short Description> * Copyright (C) 2024 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #include <blt/gp/transformers.h> #include <blt/gp/program.h> #include <blt/std/ranges.h> #include <blt/std/utility.h> #include <algorithm> #include <blt/std/memory.h> #include <blt/profiling/profiler_v2.h> #include <random> namespace blt::gp { #if BLT_DEBUG_LEVEL >= 2 || defined(BLT_TRACK_ALLOCATIONS) std::atomic_uint64_t mutate_point_counter = 0; std::atomic_uint64_t mutate_expression_counter = 0; std::atomic_uint64_t mutate_adjust_counter = 0; std::atomic_uint64_t mutate_sub_func_counter = 0; std::atomic_uint64_t mutate_jump_counter = 0; std::atomic_uint64_t mutate_copy_counter = 0; inline void print_mutate_stats() { std::cerr << "Mutation statistics (Total: " << (mutate_point_counter + mutate_expression_counter + mutate_adjust_counter + mutate_sub_func_counter + mutate_jump_counter + mutate_copy_counter) << "):" << std::endl; std::cerr << "\tSuccessful Point Mutations: " << mutate_point_counter << std::endl; std::cerr << "\tSuccessful Expression Mutations: " << mutate_expression_counter << std::endl; std::cerr << "\tSuccessful Adjust Mutations: " << mutate_adjust_counter << std::endl; std::cerr << "\tSuccessful Sub Func Mutations: " << mutate_sub_func_counter << std::endl; std::cerr << "\tSuccessful Jump Mutations: " << mutate_jump_counter << std::endl; std::cerr << "\tSuccessful Copy Mutations: " << mutate_copy_counter << std::endl; } #ifdef BLT_TRACK_ALLOCATIONS struct run_me_baby { ~run_me_baby() { print_mutate_stats(); } }; run_me_baby this_will_run_when_program_exits; #endif #endif grow_generator_t grow_generator; mutation_t::config_t::config_t(): generator(grow_generator) { } bool crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) // NOLINT { if (p1.size() < config.min_tree_size || p2.size() < config.min_tree_size) return false; std::optional<crossover_point_t> point; if (config.traverse) point = get_crossover_point_traverse(p1, p2); else point = get_crossover_point(p1, p2); if (!point) return false; // TODO: more crossover! switch (program.get_random().get_u32(0, 2)) { case 0: case 1: c1.swap_subtrees(point->p1_crossover_point, c2, point->p2_crossover_point); break; default: #if BLT_DEBUG_LEVEL > 0 BLT_ABORT("This place should be unreachable!"); #else BLT_UNREACHABLE; #endif } #if BLT_DEBUG_LEVEL >= 2 if (!c1.check(detail::debug::context_ptr) || !c2.check(detail::debug::context_ptr)) throw std::runtime_error("Tree check failed"); #endif return true; } std::optional<crossover_t::crossover_point_t> crossover_t::get_crossover_point(const tree_t& c1, const tree_t& c2) const { auto first = c1.select_subtree(config.terminal_chance); auto second = c2.select_subtree(first.type, config.max_crossover_tries, config.terminal_chance); if (!second) return {}; return {{first, *second}}; } std::optional<crossover_t::crossover_point_t> crossover_t::get_crossover_point_traverse(const tree_t& c1, const tree_t& c2) const { auto c1_point_o = get_point_traverse_retry(c1, {}); if (!c1_point_o) return {}; auto c2_point_o = get_point_traverse_retry(c2, c1_point_o->type); if (!c2_point_o) return {}; return {{*c1_point_o, *c2_point_o}}; } std::optional<tree_t::subtree_point_t> crossover_t::get_point_traverse_retry(const tree_t& t, const std::optional<type_id> type) const { if (type) return t.select_subtree_traverse(*type, config.max_crossover_tries, config.terminal_chance, config.depth_multiplier); return t.select_subtree_traverse(config.terminal_chance, config.depth_multiplier); } bool mutation_t::apply(gp_program& program, const tree_t&, tree_t& c) { // TODO: options for this? mutate_point(program, c, c.select_subtree()); return true; } size_t mutation_t::mutate_point(gp_program& program, tree_t& c, const tree_t::subtree_point_t node) const { auto& new_tree = tree_t::get_thread_local(program); config.generator.get().generate(new_tree, {program, node.type, config.replacement_min_depth, config.replacement_max_depth}); c.replace_subtree(node, new_tree); // this will check to make sure that the tree is in a correct and executable state. it requires that the evaluation is context free! #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { print_mutate_stats(); throw std::runtime_error("Mutate Point tree check failed"); } #endif #if defined(BLT_TRACK_ALLOCATIONS) || BLT_DEBUG_LEVEL >= 2 ++mutate_point_counter; #endif return node.pos + new_tree.size(); } bool advanced_mutation_t::apply(gp_program& program, [[maybe_unused]] const tree_t& p, tree_t& c) { for (size_t c_node = 0; c_node < c.size(); c_node++) { #if BLT_DEBUG_LEVEL >= 2 auto c_copy = c; #endif if (!program.get_random().choice(per_node_mutation_chance / static_cast<double>(c.size()))) continue; // select an operator to apply auto selected_point = static_cast<i32>(mutation_operator::COPY); auto choice = program.get_random().get_double(); for (const auto& [index, value] : enumerate(mutation_operator_chances)) { if (choice <= value) { selected_point = static_cast<i32>(index); break; } } switch (static_cast<mutation_operator>(selected_point)) { case mutation_operator::EXPRESSION: c_node += mutate_point(program, c, c.subtree_from_point(static_cast<ptrdiff_t>(c_node))); #if BLT_TRACK_ALLOCATIONS || BLT_DEBUG_LEVEL >= 2 ++mutate_expression_counter; #endif break; case mutation_operator::ADJUST: { // this is going to be evil >:3 const auto& node = c.get_operator(c_node); if (!node.is_value()) { auto& current_func_info = program.get_operator_info(node.id()); operator_id random_replacement = program.get_random().select( program.get_type_non_terminals(current_func_info.return_type.id)); auto& replacement_func_info = program.get_operator_info(random_replacement); // cache memory used for offset data. thread_local tracked_vector<tree_t::child_t> children_data; children_data.clear(); c.find_child_extends(children_data, c_node, current_func_info.argument_types.size()); for (const auto& [index, val] : blt::enumerate(replacement_func_info.argument_types)) { // need to generate replacement. if (index < current_func_info.argument_types.size() && val.id != current_func_info.argument_types[index].id) { // TODO: new config? auto& tree = tree_t::get_thread_local(program); config.generator.get().generate(tree, {program, val.id, config.replacement_min_depth, config.replacement_max_depth}); auto& [child_start, child_end] = children_data[children_data.size() - 1 - index]; c.replace_subtree(c.subtree_from_point(child_start), child_end, tree); // shift over everybody after. if (index > 0) { // don't need to update if the index is the last for (auto& new_child : iterate(children_data.end() - static_cast<ptrdiff_t>(index), children_data.end())) { // remove the old tree size, then add the new tree size to get the correct positions. new_child.start = new_child.start - (child_end - child_start) + static_cast<ptrdiff_t>(tree.size()); new_child.end = new_child.end - (child_end - child_start) + static_cast<ptrdiff_t>(tree.size()); } } child_end = static_cast<ptrdiff_t>(child_start + tree.size()); } } if (current_func_info.argc.argc > replacement_func_info.argc.argc) { auto end_index = children_data[(current_func_info.argc.argc - replacement_func_info.argc.argc) - 1].end; auto start_index = children_data.begin()->start; c.delete_subtree(tree_t::subtree_point_t(start_index), end_index); } else if (current_func_info.argc.argc == replacement_func_info.argc.argc) { // exactly enough args // return types should have been replaced if needed. this part should do nothing? } else { // not enough args size_t start_index = c_node + 1; // size_t total_bytes_after = c.total_value_bytes(start_index); // TODO: transactions? // auto move = c.temporary_move(total_bytes_after); for (ptrdiff_t i = static_cast<ptrdiff_t>(replacement_func_info.argc.argc) - 1; i >= current_func_info.argc.argc; i--) { auto& tree = tree_t::get_thread_local(program); config.generator.get().generate(tree, { program, replacement_func_info.argument_types[i].id, config.replacement_min_depth, config.replacement_max_depth }); start_index = c.insert_subtree(tree_t::subtree_point_t(static_cast<ptrdiff_t>(start_index)), tree); } } // now finally update the type. c.modify_operator(c_node, random_replacement, replacement_func_info.return_type); } #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { std::cout << "Parent: " << std::endl; c_copy.print(std::cout, false, false); std::cout << "Child Values:" << std::endl; c.print(std::cout, false, false); std::cout << std::endl; print_mutate_stats(); BLT_ABORT("Adjust Tree Check Failed."); } #endif #if defined(BLT_TRACK_ALLOCATIONS) || BLT_DEBUG_LEVEL >= 2 ++mutate_adjust_counter; #endif } break; case mutation_operator::SUB_FUNC: { auto& current_func_info = program.get_operator_info(c.get_operator(c_node).id()); // need to: // mutate the current function. // current function is moved to one of the arguments. // other arguments are generated. // get a replacement which returns the same type. auto& non_terminals = program.get_type_non_terminals(current_func_info.return_type.id); if (non_terminals.empty()) continue; operator_id random_replacement = program.get_random().select(non_terminals); size_t arg_position = 0; do { auto& replacement_func_info = program.get_operator_info(random_replacement); for (const auto& [index, v] : enumerate(replacement_func_info.argument_types)) { if (v.id == current_func_info.return_type.id) { arg_position = index; goto exit; } } random_replacement = program.get_random().select(program.get_type_non_terminals(current_func_info.return_type.id)); } while (true); exit: auto& replacement_func_info = program.get_operator_info(random_replacement); auto new_argc = replacement_func_info.argc.argc; // replacement function should be valid. let's make a copy of us. auto current_end = c.find_endpoint(static_cast<ptrdiff_t>(c_node)); // size_t for_bytes = c.total_value_bytes(c_node, current_end); // size_t after_bytes = c.total_value_bytes(current_end); auto size = current_end - c_node; // auto combined_ptr = get_thread_pointer_for_size<struct SUB_FUNC_FOR>(for_bytes + after_bytes); // vals.copy_to(combined_ptr, for_bytes + after_bytes); // vals.pop_bytes(static_cast<ptrdiff_t>(for_bytes + after_bytes)); size_t start_index = c_node; for (ptrdiff_t i = new_argc - 1; i > static_cast<ptrdiff_t>(arg_position); i--) { auto& tree = tree_t::get_thread_local(program); config.generator.get().generate(tree, { program, replacement_func_info.argument_types[i].id, config.replacement_min_depth, config.replacement_max_depth }); start_index = c.insert_subtree(tree_t::subtree_point_t(static_cast<ptrdiff_t>(start_index)), tree); } start_index += size; // vals.copy_from(combined_ptr, for_bytes); for (blt::ptrdiff_t i = static_cast<blt::ptrdiff_t>(arg_position) - 1; i >= 0; i--) { auto& tree = tree_t::get_thread_local(program); config.generator.get().generate(tree, { program, replacement_func_info.argument_types[i].id, config.replacement_min_depth, config.replacement_max_depth }); start_index = c.insert_subtree(tree_t::subtree_point_t(static_cast<ptrdiff_t>(start_index)), tree); } // vals.copy_from(combined_ptr + for_bytes, after_bytes); c.insert_operator(c_node, { program.get_typesystem().get_type(replacement_func_info.return_type).size(), random_replacement, program.is_operator_ephemeral(random_replacement), program.get_operator_flags(random_replacement) }); #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { std::cout << "Parent: " << std::endl; p.print(std::cout, false, false); std::cout << "Child:" << std::endl; c.print(std::cout, false, false); std::cout << std::endl; print_mutate_stats(); BLT_ABORT("SUB_FUNC Tree Check Failed."); } #endif #if defined(BLT_TRACK_ALLOCATIONS) || BLT_DEBUG_LEVEL >= 2 ++mutate_sub_func_counter; #endif } break; case mutation_operator::JUMP_FUNC: { auto& info = program.get_operator_info(c.get_operator(c_node).id()); size_t argument_index = -1ul; for (const auto& [index, v] : enumerate(info.argument_types)) { if (v.id == info.return_type.id) { argument_index = index; break; } } if (argument_index == -1ul) continue; thread_local tracked_vector<tree_t::child_t> child_data; child_data.clear(); c.find_child_extends(child_data, c_node, info.argument_types.size()); auto child_index = child_data.size() - 1 - argument_index; const auto child = child_data[child_index]; thread_local tree_t child_tree{program}; c.copy_subtree(tree_t::subtree_point_t(child.start), child.end, child_tree); c.delete_subtree(tree_t::subtree_point_t(static_cast<ptrdiff_t>(c_node))); c.insert_subtree(tree_t::subtree_point_t(static_cast<ptrdiff_t>(c_node)), child_tree); child_tree.clear(program); // auto for_bytes = c.total_value_bytes(child.start, child.end); // auto after_bytes = c.total_value_bytes(child_data.back().end); // auto storage_ptr = get_thread_pointer_for_size<struct jump_func>(for_bytes + after_bytes); // vals.copy_to(storage_ptr + for_bytes, after_bytes); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(after_bytes)); // // for (auto i = static_cast<blt::ptrdiff_t>(child_data.size() - 1); i > static_cast<blt::ptrdiff_t>(child_index); i--) // { // auto& cc = child_data[i]; // auto bytes = c.total_value_bytes(cc.start, cc.end); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(bytes)); // ops.erase(ops.begin() + cc.start, ops.begin() + cc.end); // } // vals.copy_to(storage_ptr, for_bytes); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(for_bytes)); // for (auto i = static_cast<blt::ptrdiff_t>(child_index - 1); i >= 0; i--) // { // auto& cc = child_data[i]; // auto bytes = c.total_value_bytes(cc.start, cc.end); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(bytes)); // ops.erase(ops.begin() + cc.start, ops.begin() + cc.end); // } // ops.erase(ops.begin() + static_cast<blt::ptrdiff_t>(c_node)); // vals.copy_from(storage_ptr, for_bytes + after_bytes); #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { std::cout << "Parent: " << std::endl; p.print(std::cout, false, false, false, static_cast<ptrdiff_t>(c_node)); std::cout << "Child Values:" << std::endl; c.print(std::cout, false, false); std::cout << std::endl; BLT_ERROR("Failed at mutation index %lu/%lu", c_node, c.size()); print_mutate_stats(); BLT_ABORT("JUMP_FUNC Tree Check Failed."); } #endif #if defined(BLT_TRACK_ALLOCATIONS) || BLT_DEBUG_LEVEL >= 2 ++mutate_jump_counter; #endif } break; case mutation_operator::COPY: { auto& info = program.get_operator_info(c.get_operator(c_node).id()); if (c.get_operator(c_node).is_value()) continue; thread_local tracked_vector<size_t> potential_indexes; potential_indexes.clear(); const auto from_index = program.get_random().get_u64(0, info.argument_types.size()); for (const auto [index, type] : enumerate(info.argument_types)) { if (index == from_index) continue; if (info.argument_types[from_index] == type) potential_indexes.push_back(index); } if (potential_indexes.empty()) continue; const auto to_index = program.get_random().select(potential_indexes); thread_local tracked_vector<tree_t::child_t> child_data; child_data.clear(); c.find_child_extends(child_data, c_node, info.argument_types.size()); const auto child_from_index = child_data.size() - 1 - from_index; const auto child_to_index = child_data.size() - 1 - to_index; const auto& [from_start, from_end] = child_data[child_from_index]; const auto& [to_start, to_end] = child_data[child_to_index]; thread_local tree_t copy_tree{program}; c.copy_subtree(tree_t::subtree_point_t{from_start}, from_end, copy_tree); c.replace_subtree(tree_t::subtree_point_t{to_start}, to_end, copy_tree); copy_tree.clear(program); #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { std::cout << "Parent: " << std::endl; p.print(std::cout, false, false); std::cout << "Child Values:" << std::endl; c.print(std::cout, false, false); std::cout << std::endl; print_mutate_stats(); BLT_ABORT("COPY Tree Check Failed."); } #endif #if defined(BLT_TRACK_ALLOCATIONS) || BLT_DEBUG_LEVEL >= 2 ++mutate_copy_counter; #endif // size_t from_bytes = c.total_value_bytes(from_child.start, from_child.end); // size_t after_from_bytes = c.total_value_bytes(from_child.end); // size_t to_bytes = c.total_value_bytes(to_child.start, to_child.end); // size_t after_to_bytes = c.total_value_bytes(to_child.end); // // auto after_bytes = std::max(after_from_bytes, after_to_bytes); // // auto from_ptr = get_thread_pointer_for_size<struct copy>(from_bytes); // auto after_ptr = get_thread_pointer_for_size<struct copy_after>(after_bytes); // // vals.copy_to(after_ptr, after_from_bytes); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(after_from_bytes)); // vals.copy_to(from_ptr, from_bytes); // vals.copy_from(after_ptr, after_from_bytes); // // vals.copy_to(after_ptr, after_to_bytes); // vals.pop_bytes(static_cast<blt::ptrdiff_t>(after_to_bytes + to_bytes)); // // vals.copy_from(from_ptr, from_bytes); // vals.copy_from(after_ptr, after_to_bytes); // // static thread_local tracked_vector<op_container_t> op_copy; // op_copy.clear(); // op_copy.insert(op_copy.begin(), ops.begin() + from_child.start, ops.begin() + from_child.end); // // ops.erase(ops.begin() + to_child.start, ops.begin() + to_child.end); // ops.insert(ops.begin() + to_child.start, op_copy.begin(), op_copy.end()); } break; case mutation_operator::END: default: #if BLT_DEBUG_LEVEL > 1 BLT_ABORT("You shouldn't be able to get here!"); #else BLT_UNREACHABLE; #endif } } #if BLT_DEBUG_LEVEL >= 2 if (!c.check(detail::debug::context_ptr)) { std::cout << "Parent: " << std::endl; p.print(std::cout, false, false); std::cout << "Child Values:" << std::endl; c.print(std::cout, false, false); std::cout << std::endl; BLT_ABORT("Advanced Mutation Tree Check Failed."); } #endif return true; } }