From f983c0fb627189e77f6f3c5b4cae7ccaa4a9c289 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 8 Jul 2024 22:20:51 -0400 Subject: [PATCH] selection, add lambdas to operators, few tests, mutation --- .idea/scopes/gp_progject.xml | 3 + CMakeLists.txt | 4 +- examples/gp_test_4.cpp | 2 +- examples/gp_test_5.cpp | 10 +- examples/gp_test_6.cpp | 143 ++++++++++++++++++++++++++++ examples/gp_test_7.cpp | 126 ++++++++++++++++++++++++ include/blt/gp/fwdecl.h | 3 +- include/blt/gp/generators.h | 4 + include/blt/gp/operations.h | 22 ++--- include/blt/gp/program.h | 174 ++++++++++++++++++++++++++++------ include/blt/gp/selection.h | 84 ++++++++++++++++ include/blt/gp/transformers.h | 41 ++++++++ include/blt/gp/tree.h | 80 +++++++++++++++- src/generators.cpp | 16 ++-- src/program.cpp | 37 +++++++- src/selection.cpp | 88 +++++++++++++++++ src/transformers.cpp | 77 ++++++++++++++- 17 files changed, 856 insertions(+), 58 deletions(-) create mode 100644 .idea/scopes/gp_progject.xml create mode 100644 examples/gp_test_6.cpp create mode 100644 examples/gp_test_7.cpp create mode 100644 include/blt/gp/selection.h create mode 100644 src/selection.cpp diff --git a/.idea/scopes/gp_progject.xml b/.idea/scopes/gp_progject.xml new file mode 100644 index 0000000..f02f94e --- /dev/null +++ b/.idea/scopes/gp_progject.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index dc51476..b00a43b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(blt-gp VERSION 0.0.50) +project(blt-gp VERSION 0.0.51) include(CTest) @@ -77,5 +77,7 @@ if (${BUILD_EXAMPLES}) blt_add_example(blt-gp3 examples/gp_test_3.cpp) blt_add_example(blt-gp4 examples/gp_test_4.cpp) blt_add_example(blt-gp5 examples/gp_test_5.cpp) + blt_add_example(blt-gp6 examples/gp_test_6.cpp) + blt_add_example(blt-gp7 examples/gp_test_7.cpp) endif () \ No newline at end of file diff --git a/examples/gp_test_4.cpp b/examples/gp_test_4.cpp index d350006..88e1bdc 100644 --- a/examples/gp_test_4.cpp +++ b/examples/gp_test_4.cpp @@ -78,7 +78,7 @@ int main() auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type().id(), 500, 3, 10}); - for (auto& tree : pop.getIndividuals()) + for (auto& tree : pop.for_each_tree()) { auto value = tree.get_evaluation_value(nullptr); diff --git a/examples/gp_test_5.cpp b/examples/gp_test_5.cpp index 2be92bc..3bc1ac5 100644 --- a/examples/gp_test_5.cpp +++ b/examples/gp_test_5.cpp @@ -116,7 +116,7 @@ int main() BLT_INFO("Pre-Crossover:"); for (auto& tree : pop.getIndividuals()) { - auto f = tree.get_evaluation_value(nullptr); + auto f = tree.tree.get_evaluation_value(nullptr); pre.push_back(f); BLT_TRACE(f); } @@ -134,7 +134,7 @@ int main() second = dist(random); } while (second == first); - auto results = crossover.apply(program, ind[first], ind[second]); + auto results = crossover.apply(program, ind[first].tree, ind[second].tree); if (results.has_value()) { // bool print_literals = true; @@ -149,8 +149,8 @@ int main() // results->child1.print(program, std::cout, print_literals, pretty_print, print_returns); // BLT_TRACE("Child 2: %f", results->child2.get_evaluation_value(nullptr)); // results->child2.print(program, std::cout, print_literals, pretty_print, print_returns); - new_pop.getIndividuals().push_back(std::move(results->child1)); - new_pop.getIndividuals().push_back(std::move(results->child2)); + new_pop.getIndividuals().push_back({std::move(results->child1)}); + new_pop.getIndividuals().push_back({std::move(results->child2)}); } else { switch (results.error()) @@ -169,7 +169,7 @@ int main() } BLT_INFO("Post-Crossover:"); - for (auto& tree : new_pop.getIndividuals()) + for (auto& tree : new_pop.for_each_tree()) { auto f = tree.get_evaluation_value(nullptr); pos.push_back(f); diff --git a/examples/gp_test_6.cpp b/examples/gp_test_6.cpp new file mode 100644 index 0000000..903ac6f --- /dev/null +++ b/examples/gp_test_6.cpp @@ -0,0 +1,143 @@ +/* + * + * 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 . + */ +/* + * + * 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 . + */ +#include +#include +#include +#include +#include + +static constexpr long SEED = 41912; + + +blt::gp::type_provider type_system; +blt::gp::gp_program program(type_system, std::mt19937_64{SEED}); // NOLINT + +blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); // 0 +blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); // 1 +blt::gp::operation_t mul([](float a, float b) { return a * b; }, "mul"); // 2 +blt::gp::operation_t pro_div([](float a, float b) { return b == 0 ? 0.0f : a / b; }, "div"); // 3 + +blt::gp::operation_t op_if([](bool b, float a, float c) { return b ? a : c; }, "if"); // 4 +blt::gp::operation_t eq_f([](float a, float b) { return a == b; }, "eq_f"); // 5 +blt::gp::operation_t eq_b([](bool a, bool b) { return a == b; }, "eq_b"); // 6 +blt::gp::operation_t lt([](float a, float b) { return a < b; }, "lt"); // 7 +blt::gp::operation_t gt([](float a, float b) { return a > b; }, "gt"); // 8 +blt::gp::operation_t op_and([](bool a, bool b) { return a && b; }, "and"); // 9 +blt::gp::operation_t op_or([](bool a, bool b) { return a || b; }, "or"); // 10 +blt::gp::operation_t op_xor([](bool a, bool b) { return static_cast(a ^ b); }, "xor"); // 11 +blt::gp::operation_t op_not([](bool b) { return !b; }, "not"); // 12 + +blt::gp::operation_t lit([]() { // 13 + //static std::uniform_real_distribution dist(-32000, 32000); + static std::uniform_real_distribution dist(0.0f, 10.0f); + return dist(program.get_random()); +}, "lit"); + +/** + * This is a test using multiple types with blt::gp + */ +int main() +{ + type_system.register_type(); + type_system.register_type(); + + blt::gp::operator_builder builder{type_system}; + builder.add_operator(add); + builder.add_operator(sub); + builder.add_operator(mul); + builder.add_operator(pro_div); + + builder.add_operator(op_if); + builder.add_operator(eq_f); + builder.add_operator(eq_b); + builder.add_operator(lt); + builder.add_operator(gt); + builder.add_operator(op_and); + builder.add_operator(op_or); + builder.add_operator(op_xor); + builder.add_operator(op_not); + + builder.add_operator(lit, true); + + program.set_operations(builder.build()); + + blt::gp::ramped_half_initializer_t pop_init; + + auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type().id(), 500, 3, 10}); + + blt::gp::population_t new_pop; + blt::gp::mutation_t mutator; + blt::gp::grow_generator_t generator; + + std::vector pre; + std::vector pos; + + BLT_INFO("Pre-Mutation:"); + for (auto& tree : pop.for_each_tree()) + { + auto f = tree.get_evaluation_value(nullptr); + pre.push_back(f); + BLT_TRACE(f); + } + BLT_INFO("Mutation:"); + for (auto& tree : pop.for_each_tree()) + { + new_pop.getIndividuals().push_back({mutator.apply(program, generator, tree)}); + } + BLT_INFO("Post-Mutation"); + for (auto& tree : new_pop.for_each_tree()) + { + auto f = tree.get_evaluation_value(nullptr); + pos.push_back(f); + BLT_TRACE(f); + } + + BLT_INFO("Stats:"); + blt::size_t eq = 0; + for (const auto& v : pos) + { + for (const auto m : pre) + { + if (v == m) + { + eq++; + break; + } + } + } + BLT_INFO("Equal values: %ld", eq); + + return 0; +} diff --git a/examples/gp_test_7.cpp b/examples/gp_test_7.cpp new file mode 100644 index 0000000..6778d4d --- /dev/null +++ b/examples/gp_test_7.cpp @@ -0,0 +1,126 @@ +/* + * + * 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 . + */ +#include +#include +#include +#include +#include + +static constexpr long SEED = 41912; + + +blt::gp::type_provider type_system; +blt::gp::gp_program program(type_system, std::mt19937_64{SEED}); // NOLINT + +blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); // 0 +blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); // 1 +blt::gp::operation_t mul([](float a, float b) { return a * b; }, "mul"); // 2 +blt::gp::operation_t pro_div([](float a, float b) { return b == 0 ? 0.0f : a / b; }, "div"); // 3 + +blt::gp::operation_t op_if([](bool b, float a, float c) { return b ? a : c; }, "if"); // 4 +blt::gp::operation_t eq_f([](float a, float b) { return a == b; }, "eq_f"); // 5 +blt::gp::operation_t eq_b([](bool a, bool b) { return a == b; }, "eq_b"); // 6 +blt::gp::operation_t lt([](float a, float b) { return a < b; }, "lt"); // 7 +blt::gp::operation_t gt([](float a, float b) { return a > b; }, "gt"); // 8 +blt::gp::operation_t op_and([](bool a, bool b) { return a && b; }, "and"); // 9 +blt::gp::operation_t op_or([](bool a, bool b) { return a || b; }, "or"); // 10 +blt::gp::operation_t op_xor([](bool a, bool b) { return static_cast(a ^ b); }, "xor"); // 11 +blt::gp::operation_t op_not([](bool b) { return !b; }, "not"); // 12 + +blt::gp::operation_t lit([]() { // 13 + //static std::uniform_real_distribution dist(-32000, 32000); + static std::uniform_real_distribution dist(0.0f, 10.0f); + return dist(program.get_random()); +}, "lit"); + +/** + * This is a test using multiple types with blt::gp + */ +int main() +{ + type_system.register_type(); + type_system.register_type(); + + blt::gp::operator_builder builder{type_system}; + builder.add_operator(add); + builder.add_operator(sub); + builder.add_operator(mul); + builder.add_operator(pro_div); + + builder.add_operator(op_if); + builder.add_operator(eq_f); + builder.add_operator(eq_b); + builder.add_operator(lt); + builder.add_operator(gt); + builder.add_operator(op_and); + builder.add_operator(op_or); + builder.add_operator(op_xor); + builder.add_operator(op_not); + + builder.add_operator(lit, true); + + program.set_operations(builder.build()); + + blt::gp::ramped_half_initializer_t pop_init; + + auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type().id(), 500, 3, 10}); + + blt::gp::population_t new_pop; + blt::gp::mutation_t mutator; + blt::gp::grow_generator_t generator; + + std::vector pre; + std::vector pos; + + BLT_INFO("Pre-Mutation:"); + for (auto& tree : pop.for_each_tree()) + { + auto f = tree.get_evaluation_value(nullptr); + pre.push_back(f); + BLT_TRACE(f); + } + BLT_INFO("Mutation:"); + for (auto& tree : pop.for_each_tree()) + { + new_pop.getIndividuals().push_back({mutator.apply(program, generator, tree)}); + } + BLT_INFO("Post-Mutation"); + for (auto& tree : new_pop.for_each_tree()) + { + auto f = tree.get_evaluation_value(nullptr); + pos.push_back(f); + BLT_TRACE(f); + } + + BLT_INFO("Stats:"); + blt::size_t eq = 0; + for (const auto& v : pos) + { + for (const auto m : pre) + { + if (v == m) + { + eq++; + break; + } + } + } + BLT_INFO("Equal values: %ld", eq); + + return 0; +} \ No newline at end of file diff --git a/include/blt/gp/fwdecl.h b/include/blt/gp/fwdecl.h index bfde2e7..ae5e769 100644 --- a/include/blt/gp/fwdecl.h +++ b/include/blt/gp/fwdecl.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace blt::gp { @@ -55,7 +56,7 @@ namespace blt::gp // context*, read stack, write stack using callable_t = std::function; // to, from - using transfer_t = std::function; + using transfer_t = std::function>, stack_allocator&)>; // debug function, using print_func_t = std::function; } diff --git a/include/blt/gp/generators.h b/include/blt/gp/generators.h index 68e8c4c..fac1ec7 100644 --- a/include/blt/gp/generators.h +++ b/include/blt/gp/generators.h @@ -52,6 +52,8 @@ namespace blt::gp { public: virtual tree_t generate(const generator_arguments& args) = 0; + + virtual ~tree_generator_t() = default; }; class grow_generator_t : public tree_generator_t @@ -70,6 +72,8 @@ namespace blt::gp { public: virtual population_t generate(const initializer_arguments& args) = 0; + + virtual ~population_initializer_t() = default; }; class grow_initializer_t : public population_initializer_t diff --git a/include/blt/gp/operations.h b/include/blt/gp/operations.h index 304bb75..ddd7c53 100644 --- a/include/blt/gp/operations.h +++ b/include/blt/gp/operations.h @@ -107,14 +107,14 @@ namespace blt::gp using call_with::call_with; }; - template + template class operation_t; - template - class operation_t + template + class operation_t { public: - using function_t = std::function; + using function_t = ArgType; constexpr operation_t(const operation_t& copy) = default; @@ -183,24 +183,24 @@ namespace blt::gp std::optional name; }; - template - class operation_t : public operation_t + template + class operation_t : public operation_t { public: - using operation_t::operation_t; + using operation_t::operation_t; }; template - operation_t(Lambda) -> operation_t; + operation_t(Lambda) -> operation_t; template - operation_t(Return(*)(Args...)) -> operation_t; + operation_t(Return(*)(Args...)) -> operation_t; template - operation_t(Lambda, std::optional) -> operation_t; + operation_t(Lambda, std::optional) -> operation_t; template - operation_t(Return(*)(Args...), std::optional) -> operation_t; + operation_t(Return(*)(Args...), std::optional) -> operation_t; // templat\e // operation_t make_operator(Return (Class::*)(Args...) const lambda) diff --git a/include/blt/gp/program.h b/include/blt/gp/program.h index 440986e..b4ecc2b 100644 --- a/include/blt/gp/program.h +++ b/include/blt/gp/program.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include @@ -55,16 +57,6 @@ namespace blt::gp } }; - 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 - blt::u16 max_crossover_tries = 5; - // if we fail to find a point in the tree, should we search forward from the last point to the end of the operators? - bool should_crossover_try_forward = false; - // avoid selecting terminals when doing crossover - bool avoid_terminals = false; - }; - struct operator_info { // types of the arguments @@ -87,10 +79,6 @@ namespace blt::gp blt::expanding_buffer>> operators_ordered_terminals; // indexed from OPERATOR ID (operator number) blt::hashset_t static_types; -// blt::expanding_buffer> argument_types; -// blt::expanding_buffer operator_argc; -// std::vector operators; -// std::vector transfer_funcs; std::vector operators; std::vector print_funcs; std::vector> names; @@ -107,8 +95,8 @@ namespace blt::gp explicit operator_builder(type_provider& system): system(system) {} - template - operator_builder& add_operator(const operation_t& op, bool is_static = false) + template + operator_builder& add_operator(const operation_t& op, bool is_static = false) { auto return_type_id = system.get_type().id(); auto operator_id = blt::gp::operator_id(storage.operators.size()); @@ -131,13 +119,21 @@ namespace blt::gp BLT_ASSERT(info.argc.argc_context - info.argc.argc <= 1 && "Cannot pass multiple context as arguments!"); info.function = op.template make_callable(); - info.transfer = [](stack_allocator& to, stack_allocator& from) { + info.transfer = [](std::optional> to, stack_allocator& from) { #if BLT_DEBUG_LEVEL >= 3 auto value = from.pop(); //BLT_TRACE_STREAM << value << "\n"; - to.push(value); + if (to){ + to->get().push(value); + } #else - to.push(from.pop()); + if (to) + { + to->get().push(from.pop()); + } else + { + from.pop(); + } #endif }; @@ -231,6 +227,68 @@ namespace blt::gp class gp_program { public: + struct config_t + { + blt::size_t population_size = 500; + blt::size_t initial_min_tree_size = 3; + blt::size_t initial_max_tree_size = 10; + + std::reference_wrapper mutator; + std::reference_wrapper crossover; + std::reference_wrapper pop_initializer; + + // default config (ramped half-and-half init) or for buildering + config_t(); + + // default config with a user specified initializer + config_t(const std::reference_wrapper& popInitializer); // NOLINT + + config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize); + + config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize, + const std::reference_wrapper& popInitializer); + + config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize, + const std::reference_wrapper& mutator, const std::reference_wrapper& crossover, + const std::reference_wrapper& popInitializer); + + config_t& set_pop_size(blt::size_t pop) + { + population_size = pop; + return *this; + } + + config_t& set_initial_min_tree_size(blt::size_t size) + { + initial_min_tree_size = size; + return *this; + } + + config_t& set_initial_max_tree_size(blt::size_t size) + { + initial_max_tree_size = size; + return *this; + } + + config_t& set_crossover(crossover_t& ref) + { + crossover = ref; + return *this; + } + + config_t& set_mutation(mutation_t& ref) + { + mutator = ref; + return *this; + } + + config_t& set_initializer(population_initializer_t& ref) + { + pop_initializer = ref; + return *this; + } + }; + /** * Note about context size: This is required as context is passed to every operator in the GP tree, this context will be provided by your * call to one of the evaluator functions. This was the nicest way to provide this as C++ lacks reflection @@ -243,7 +301,69 @@ namespace blt::gp system(system), engine(engine) {} - void generate_tree(); + explicit gp_program(type_provider& system, std::mt19937_64 engine, config_t config): + system(system), engine(engine), config(config) + {} + + void generate_population(type_id root_type); + + + /** + * takes in a lambda for the fitness evaluation function (must return a value convertable to double) + * The lambda must accept a tree for evaluation, container for evaluation context, and a index into that container (current tree) + * + * Container must be concurrently accessible from multiple threads using operator[] + * + * NOTE: 0 is considered the best, in terms of standardized and adjusted fitness + */ + template + void evaluate_fitness(Lambda&& fitness_function, Container& result_storage) + { + for (const auto& ind : blt::enumerate(current_pop.getIndividuals())) + ind.second.raw_fitness = static_cast(fitness_function(ind.second.tree, result_storage, ind.first)); + double min = 0; + for (auto& ind : current_pop.getIndividuals()) + { + if (ind.raw_fitness < min) + min = ind.raw_fitness; + } + + double overall_fitness = 0; + double best_fitness = 2; + double worst_fitness = 0; + individual* best = nullptr; + individual* worst = nullptr; + + auto diff = -min; + for (auto& ind : current_pop.getIndividuals()) + { + ind.standardized_fitness = ind.raw_fitness + diff; + ind.adjusted_fitness = 1.0 / (1.0 + ind.standardized_fitness); + + if (ind.adjusted_fitness > worst_fitness) + { + worst_fitness = ind.adjusted_fitness; + worst = &ind; + } + + if (ind.adjusted_fitness < best_fitness) + { + best_fitness = ind.adjusted_fitness; + best = &ind; + } + + overall_fitness += ind.adjusted_fitness; + } + + current_stats = {overall_fitness, overall_fitness / static_cast(config.population_size), best_fitness, worst_fitness, best, + worst}; + } + + void next_generation() + { + current_pop = next_pop; + current_generation++; + } [[nodiscard]] inline std::mt19937_64& get_random() { @@ -257,7 +377,7 @@ namespace blt::gp } /** - * @param cutoff precent in floating point form chance of the event happening. + * @param cutoff percent in floating point form chance of the event happening. * @return */ [[nodiscard]] inline bool choice(double cutoff) @@ -326,20 +446,20 @@ namespace blt::gp { storage = std::move(op); } - - [[nodiscard]] inline const config_t& get_config() const - { - return config; - } private: type_provider& system; + blt::gp::stack_allocator alloc; - config_t config; operator_storage storage; + population_t current_pop; + population_stats current_stats; + population_t next_pop; + blt::size_t current_generation = 0; std::mt19937_64 engine; + config_t config; }; } diff --git a/include/blt/gp/selection.h b/include/blt/gp/selection.h new file mode 100644 index 0000000..1df6232 --- /dev/null +++ b/include/blt/gp/selection.h @@ -0,0 +1,84 @@ +#pragma once +/* + * 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 . + */ + +#ifndef BLT_GP_SELECTION_H +#define BLT_GP_SELECTION_H + +#include +#include +#include + +namespace blt::gp +{ + + class selection_t + { + public: + /** + * @param program gp program to select with, used in randoms + * @param pop population to select from + * @param stats the populations statistics + * @return + */ + virtual tree_t& select(gp_program& program, population_t& pop, population_stats& stats) = 0; + + virtual ~selection_t() = default; + }; + + class select_best_t : public selection_t + { + public: + tree_t& select(gp_program& program, population_t& pop, population_stats& stats) final; + }; + + class select_worst_t : public selection_t + { + public: + tree_t& select(gp_program& program, population_t& pop, population_stats& stats) final; + }; + + class select_random_t : public selection_t + { + public: + tree_t& select(gp_program& program, population_t& pop, population_stats& stats) final; + }; + + class select_tournament_t : public selection_t + { + public: + explicit select_tournament_t(blt::size_t selection_size = 3): selection_size(selection_size) + { + if (selection_size < 1) + BLT_ABORT("Unable to select with this size. Must select at least 1 individual!"); + } + + tree_t& select(gp_program& program, population_t& pop, population_stats& stats) final; + + private: + blt::size_t selection_size; + }; + + class select_fitness_proportionate_t : public selection_t + { + public: + tree_t& select(gp_program& program, population_t& pop, population_stats& stats) final; + }; + +} + +#endif //BLT_GP_SELECTION_H diff --git a/include/blt/gp/transformers.h b/include/blt/gp/transformers.h index 25b103e..3175137 100644 --- a/include/blt/gp/transformers.h +++ b/include/blt/gp/transformers.h @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace blt::gp @@ -40,6 +41,20 @@ namespace blt::gp tree_t child1; tree_t child2; }; + 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 + blt::u16 max_crossover_tries = 5; + // if we fail to find a point in the tree, should we search forward from the last point to the end of the operators? + bool should_crossover_try_forward = false; + // avoid selecting terminals when doing crossover + bool avoid_terminals = false; + }; + + crossover_t() = default; + + explicit crossover_t(const config_t& config): config(config) + {} /** * child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover. @@ -50,6 +65,32 @@ namespace blt::gp * @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 ~crossover_t() = default; + + private: + config_t config; + }; + + class mutation_t + { + public: + struct config_t + { + blt::size_t replacement_min_depth = 3; + blt::size_t replacement_max_depth = 7; + }; + + mutation_t() = default; + + explicit mutation_t(const config_t& config): config(config) + {} + + virtual tree_t apply(gp_program& program, tree_generator_t& generator, const tree_t& p); // NOLINT + + virtual ~mutation_t() = default; + private: + config_t config; }; } diff --git a/include/blt/gp/tree.h b/include/blt/gp/tree.h index 11e97b5..df70464 100644 --- a/include/blt/gp/tree.h +++ b/include/blt/gp/tree.h @@ -110,16 +110,92 @@ namespace blt::gp blt::size_t depth; }; + struct individual + { + tree_t tree; + double raw_fitness = 0; + double standardized_fitness = 0; + double adjusted_fitness = 0; + }; + + struct population_stats + { + double overall_fitness = 0; + double average_fitness = 0; + double best_fitness = 1; + double worst_fitness = 0; + // these will never be null unless your pop is not initialized / fitness eval was not called! + individual* best_individual = nullptr; + individual* worst_individual = nullptr; + }; + class population_t { public: - std::vector& getIndividuals() + class population_tree_iterator + { + public: + population_tree_iterator(std::vector& ind, blt::size_t pos): ind(ind), pos(pos) + {} + + auto begin() + { + return population_tree_iterator(ind, 0); + } + + auto end() + { + return population_tree_iterator(ind, ind.size()); + } + + population_tree_iterator operator++(int) + { + auto prev = pos++; + return {ind, prev}; + } + + population_tree_iterator operator++() + { + return {ind, ++pos}; + } + + tree_t& operator*() + { + return ind[pos].tree; + } + + tree_t& operator->() + { + return ind[pos].tree; + } + + friend bool operator==(population_tree_iterator a, population_tree_iterator b) + { + return a.pos == b.pos; + } + + friend bool operator!=(population_tree_iterator a, population_tree_iterator b) + { + return a.pos != b.pos; + } + + private: + std::vector& ind; + blt::size_t pos; + }; + + std::vector& getIndividuals() { return individuals; } + + population_tree_iterator for_each_tree() + { + return population_tree_iterator{individuals, 0}; + } private: - std::vector individuals; + std::vector individuals; }; } diff --git a/src/generators.cpp b/src/generators.cpp index 31cfda2..343d3b1 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -120,7 +120,7 @@ namespace blt::gp population_t pop; for (auto i = 0ul; i < args.size; i++) - pop.getIndividuals().push_back(grow.generate(args.to_gen_args())); + pop.getIndividuals().push_back({grow.generate(args.to_gen_args())}); return pop; } @@ -130,7 +130,7 @@ namespace blt::gp population_t pop; for (auto i = 0ul; i < args.size; i++) - pop.getIndividuals().push_back(full.generate(args.to_gen_args())); + pop.getIndividuals().push_back({full.generate(args.to_gen_args())}); return pop; } @@ -142,9 +142,9 @@ namespace blt::gp for (auto i = 0ul; i < args.size; i++) { if (args.program.choice()) - pop.getIndividuals().push_back(full.generate(args.to_gen_args())); + pop.getIndividuals().push_back({full.generate(args.to_gen_args())}); else - pop.getIndividuals().push_back(grow.generate(args.to_gen_args())); + pop.getIndividuals().push_back({grow.generate(args.to_gen_args())}); } return pop; @@ -162,18 +162,18 @@ namespace blt::gp for (auto i = 0ul; i < per_step; i++) { if (args.program.choice()) - pop.getIndividuals().push_back(full.generate({args.program, args.root_type, args.min_depth, depth})); + pop.getIndividuals().push_back({full.generate({args.program, args.root_type, args.min_depth, depth})}); else - pop.getIndividuals().push_back(grow.generate({args.program, args.root_type, args.min_depth, depth})); + pop.getIndividuals().push_back({grow.generate({args.program, args.root_type, args.min_depth, depth})}); } } for (auto i = 0ul; i < remainder; i++) { if (args.program.choice()) - pop.getIndividuals().push_back(full.generate(args.to_gen_args())); + pop.getIndividuals().push_back({full.generate(args.to_gen_args())}); else - pop.getIndividuals().push_back(grow.generate(args.to_gen_args())); + pop.getIndividuals().push_back({grow.generate(args.to_gen_args())}); } blt_assert(pop.getIndividuals().size() == args.size); diff --git a/src/program.cpp b/src/program.cpp index 3d821da..0fa2c15 100644 --- a/src/program.cpp +++ b/src/program.cpp @@ -19,5 +19,40 @@ namespace blt::gp { - + + // default static references for mutation, crossover, and initializer + // this is largely to not break the tests :3 + // it's also to allow for quick setup of a gp program if you don't care how crossover or mutation is handled + static mutation_t s_mutator; + static crossover_t s_crossover; + static ramped_half_initializer_t s_init; + + gp_program::config_t::config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize): + population_size(populationSize), initial_min_tree_size(initialMinTreeSize), initial_max_tree_size(initialMaxTreeSize), mutator(s_mutator), + crossover(s_crossover), pop_initializer(s_init) + {} + + gp_program::config_t::config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize, + const std::reference_wrapper& mutator, const std::reference_wrapper& crossover, + const std::reference_wrapper& popInitializer): + population_size(populationSize), initial_min_tree_size(initialMinTreeSize), initial_max_tree_size(initialMaxTreeSize), mutator(mutator), + crossover(crossover), pop_initializer(popInitializer) + {} + + gp_program::config_t::config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize, + const std::reference_wrapper& popInitializer): + population_size(populationSize), initial_min_tree_size(initialMinTreeSize), initial_max_tree_size(initialMaxTreeSize), + mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer) + {} + + gp_program::config_t::config_t(): mutator(s_mutator), crossover(s_crossover), pop_initializer(s_init) + { + + } + + gp_program::config_t::config_t(const std::reference_wrapper& popInitializer): + mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer) + { + + } } \ No newline at end of file diff --git a/src/selection.cpp b/src/selection.cpp new file mode 100644 index 0000000..3e26c6b --- /dev/null +++ b/src/selection.cpp @@ -0,0 +1,88 @@ +/* + * + * 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 . + */ +#include +#include + +namespace blt::gp +{ + + tree_t& select_best_t::select(gp_program&, population_t& pop, population_stats& stats) + { + auto& first = pop.getIndividuals()[0]; + double best_fitness = first.adjusted_fitness; + tree_t* tree = &first.tree; + for (auto& ind : pop.getIndividuals()) + { + if (ind.adjusted_fitness < best_fitness) + { + best_fitness = ind.adjusted_fitness; + tree = &ind.tree; + } + } + return *tree; + } + + tree_t& select_worst_t::select(gp_program&, population_t& pop, population_stats& stats) + { + auto& first = pop.getIndividuals()[0]; + double worst_fitness = first.adjusted_fitness; + tree_t* tree = &first.tree; + for (auto& ind : pop.getIndividuals()) + { + if (ind.adjusted_fitness > worst_fitness) + { + worst_fitness = ind.adjusted_fitness; + tree = &ind.tree; + } + } + return *tree; + } + + tree_t& select_random_t::select(gp_program& program, population_t& pop, population_stats& stats) + { + // TODO: use a more generic randomness solution. + std::uniform_int_distribution dist(0ul, pop.getIndividuals().size()); + return pop.getIndividuals()[dist(program.get_random())].tree; + } + + tree_t& select_tournament_t::select(gp_program& program, population_t& pop, population_stats& stats) + { + std::uniform_int_distribution dist(0ul, pop.getIndividuals().size()); + + auto& first = pop.getIndividuals()[dist(program.get_random())]; + individual* ind = &first; + double best_guy = first.adjusted_fitness; + for (blt::size_t i = 0; i < selection_size - 1; i++) + { + auto& sel = pop.getIndividuals()[dist(program.get_random())]; + if (sel.adjusted_fitness < best_guy) + { + best_guy = sel.adjusted_fitness; + ind = &sel; + } + } + + return ind->tree; + } + + // https://www.google.com/search?client=firefox-b-d&sca_esv=71668abf73626b35&sca_upv=1&biw=1916&bih=940&sxsrf=ADLYWIJehgPtkALJDoTgHCiO4GNeQppSeA:1720490607140&q=roulette+wheel+selection+pseudocode&uds=ADvngMgiq8uozSRb4WPAa_ESRaBJz-G_Xhk1OLU3QFjqc3o31P4ECuIkKJxHd-cR3WUe9U7VQGpI6NRaMgYiWTMd4wNofAAaNq6X4eHYpN8cR9HmTfTw0KgYC6gI4dgu-s-5mXivdsv4QxrkVAL7yMoXacJngsiMBg&udm=2&sa=X&ved=2ahUKEwig7Oj77piHAxU3D1kFHS1lAIsQxKsJegQIDBAB&ictx=0#vhid=6iCOymnPvtyy-M&vssid=mosaic + tree_t& select_fitness_proportionate_t::select(gp_program& program, population_t& pop, population_stats& stats) + { + + } +} \ No newline at end of file diff --git a/src/transformers.cpp b/src/transformers.cpp index 094de6d..acddc32 100644 --- a/src/transformers.cpp +++ b/src/transformers.cpp @@ -24,7 +24,6 @@ namespace blt::gp { blt::expected crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2) // NOLINT { - const auto& config = program.get_config(); result_t result{p1, p2}; #if BLT_DEBUG_LEVEL > 0 @@ -254,4 +253,80 @@ namespace blt::gp return result; } + + tree_t mutation_t::apply(gp_program& program, tree_generator_t& generator, const tree_t& p) + { + auto c = p; + + auto& ops = c.get_operations(); + auto& vals = c.get_values(); + + std::uniform_int_distribution point_sel_dist(0ul, ops.size() - 1); + auto point = point_sel_dist(program.get_random()); + const auto& type_info = program.get_operator_info(ops[point].id); + + blt::i64 children_left = 0; + blt::size_t index = point; + + do + { + const auto& type = program.get_operator_info(ops[index].id); + + // 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 begin_p = ops.begin() + static_cast(point); + auto end_p = ops.begin() + static_cast(index); + + stack_allocator after_stack; + //std::vector after_ops; + + for (auto it = ops.end() - 1; it != end_p - 1; it--) + { + if (it->is_value) + { + it->transfer(after_stack, vals); + //after_ops.push_back(*it); + } + } + + for (auto it = end_p - 1; it != begin_p - 1; it--) + { + if (it->is_value) + it->transfer(std::optional>{}, vals); + } + + auto before = begin_p - 1; + + ops.erase(begin_p, end_p); + + auto new_tree = generator.generate({program, type_info.return_type, config.replacement_min_depth, config.replacement_max_depth}); + + auto& new_ops = new_tree.get_operations(); + auto& new_vals = new_tree.get_values(); + + ops.insert(++before, new_ops.begin(), new_ops.end()); + + for (const auto& op : new_ops) + { + if (op.is_value) + op.transfer(vals, new_vals); + } + + auto new_end_point = point + new_ops.size(); + auto new_end_p = ops.begin() + static_cast(new_end_point); + + for (auto it = new_end_p; it != ops.end(); it++) + { + if (it->is_value) + it->transfer(vals, after_stack); + } + + return c; + } } \ No newline at end of file