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