diff --git a/.idea/editor.xml b/.idea/editor.xml
index b0d69ef..04cdbc9 100644
--- a/.idea/editor.xml
+++ b/.idea/editor.xml
@@ -240,244 +240,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b839fcc..97ee352 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,7 +27,7 @@ macro(compile_options target_name)
sanitizers(${target_name})
endmacro()
-project(blt-gp VERSION 0.3.32)
+project(blt-gp VERSION 0.4.9)
include(CTest)
@@ -122,5 +122,6 @@ if (${BUILD_GP_TESTS})
blt_add_project(blt-symbolic-regression tests/symbolic_regression_test.cpp test)
blt_add_project(blt-drop tests/drop_test.cpp test)
+ blt_add_project(blt-drop-2-type tests/2_type_drop_test.cpp test)
endif ()
\ No newline at end of file
diff --git a/commit.py b/commit.py
index b39f82d..c95a67a 100755
--- a/commit.py
+++ b/commit.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!python3
import subprocess
diff --git a/examples/symbolic_regression.h b/examples/symbolic_regression.h
index bff2ae1..d89efbc 100644
--- a/examples/symbolic_regression.h
+++ b/examples/symbolic_regression.h
@@ -20,189 +20,208 @@
#define BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H
#include "examples_base.h"
-#include
+#include
#include
#include
namespace blt::gp::example
{
- class symbolic_regression_t : public example_base_t
- {
- public:
- struct context
- {
- float x, y;
- };
+ class symbolic_regression_t : public example_base_t
+ {
+ public:
+ struct context
+ {
+ float x, y;
+ };
- private:
- bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const
- {
- constexpr static double value_cutoff = 1.e15;
- for (auto& fitness_case : training_cases)
- {
- const auto diff = std::abs(fitness_case.y - current_tree.get_evaluation_value(fitness_case));
- if (diff < value_cutoff)
- {
- fitness.raw_fitness += diff;
- if (diff <= 0.01)
- fitness.hits++;
- }
- else
- fitness.raw_fitness += value_cutoff;
- }
- fitness.standardized_fitness = fitness.raw_fitness;
- fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness));
- return static_cast(fitness.hits) == training_cases.size();
- }
+ private:
+ bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const
+ {
+ constexpr static double value_cutoff = 1.e15;
+ for (auto& fitness_case : training_cases)
+ {
+ BLT_GP_UPDATE_CONTEXT(fitness_case);
+ const auto diff = std::abs(fitness_case.y - current_tree.get_evaluation_value(fitness_case));
+ if (diff < value_cutoff)
+ {
+ fitness.raw_fitness += diff;
+ if (diff <= 0.01)
+ fitness.hits++;
+ } else
+ fitness.raw_fitness += value_cutoff;
+ }
+ fitness.standardized_fitness = fitness.raw_fitness;
+ fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness));
+ return static_cast(fitness.hits) == training_cases.size();
+ }
- static float example_function(const float x)
- {
- return x * x * x * x + x * x * x + x * x + x;
- }
+ static float example_function(const float x)
+ {
+ return x * x * x * x + x * x * x + x * x + x;
+ }
- public:
- template
- symbolic_regression_t(SEED seed, const prog_config_t& config): example_base_t{std::forward(seed), config}
- {
- BLT_INFO("Starting BLT-GP Symbolic Regression Example");
- BLT_DEBUG("Setup Fitness cases");
- for (auto& fitness_case : training_cases)
- {
- constexpr float range = 10;
- constexpr float half_range = range / 2.0;
- const auto x = program.get_random().get_float(-half_range, half_range);
- const auto y = example_function(x);
- fitness_case = {x, y};
- }
+ public:
+ template
+ symbolic_regression_t(SEED seed, const prog_config_t& config): example_base_t{std::forward(seed), config}
+ {
+ BLT_INFO("Starting BLT-GP Symbolic Regression Example");
+ BLT_DEBUG("Setup Fitness cases");
+ for (auto& fitness_case : training_cases)
+ {
+ constexpr float range = 10;
+ constexpr float half_range = range / 2.0;
+ const auto x = program.get_random().get_float(-half_range, half_range);
+ const auto y = example_function(x);
+ fitness_case = {x, y};
+ }
- fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i)
- {
- return fitness_function(t, f, i);
- };
- }
+ fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i) {
+ return fitness_function(t, f, i);
+ };
+ }
- void setup_operations()
- {
- BLT_DEBUG("Setup Types and Operators");
- static operation_t add{[](const float a, const float b) { return a + b; }, "add"};
- static operation_t sub([](const float a, const float b) { return a - b; }, "sub");
- static operation_t mul([](const float a, const float b) { return a * b; }, "mul");
- static operation_t pro_div([](const float a, const float b) { return b == 0.0f ? 0.0f : a / b; }, "div");
- static operation_t op_sin([](const float a) { return std::sin(a); }, "sin");
- static operation_t op_cos([](const float a) { return std::cos(a); }, "cos");
- static operation_t op_exp([](const float a) { return std::exp(a); }, "exp");
- static operation_t op_log([](const float a) { return a <= 0.0f ? 0.0f : std::log(a); }, "log");
- static auto lit = operation_t([this]()
- {
- return program.get_random().get_float(-1.0f, 1.0f);
- }, "lit").set_ephemeral();
+ void setup_operations()
+ {
+ BLT_DEBUG("Setup Types and Operators");
+ static operation_t add{
+ // this is the function used by the operation
+ [](const float a, const float b) {
+ return a + b;
+ },
+ // this name is optional and is used if you print an individual
+ "add"
+ };
+ static operation_t sub([](const float a, const float b) {
+ return a - b;
+ }, "sub");
+ static operation_t mul([](const float a, const float b) {
+ return a * b;
+ }, "mul");
+ static operation_t pro_div([](const float a, const float b) {
+ return b == 0.0f ? 0.0f : a / b;
+ }, "div");
+ static operation_t op_sin([](const float a) {
+ return std::sin(a);
+ }, "sin");
+ static operation_t op_cos([](const float a) {
+ return std::cos(a);
+ }, "cos");
+ static operation_t op_exp([](const float a) {
+ return std::exp(a);
+ }, "exp");
+ static operation_t op_log([](const float a) {
+ return a <= 0.0f ? 0.0f : std::log(a);
+ }, "log");
+ static auto lit = operation_t([this]() {
+ return program.get_random().get_float(-1.0f, 1.0f);
+ }, "lit").set_ephemeral();
- static operation_t op_x([](const context& context)
- {
- return context.x;
- }, "x");
+ static operation_t op_x([](const context& context) {
+ return context.x;
+ }, "x");
- operator_builder builder{};
- builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x);
- program.set_operations(builder.grab());
- }
+ operator_builder builder{};
+ builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x);
+ program.set_operations(builder.grab());
+ }
- void generate_initial_population()
- {
- BLT_DEBUG("Generate Initial Population");
- static auto sel = select_tournament_t{};
- if (crossover_sel == nullptr)
- crossover_sel = &sel;
- if (mutation_sel == nullptr)
- mutation_sel = &sel;
- if (reproduction_sel == nullptr)
- reproduction_sel = &sel;
- program.generate_population(program.get_typesystem().get_type().id(), fitness_function_ref, *crossover_sel, *mutation_sel,
- *reproduction_sel);
- }
+ void generate_initial_population()
+ {
+ BLT_DEBUG("Generate Initial Population");
+ static auto sel = select_tournament_t{};
+ if (crossover_sel == nullptr)
+ crossover_sel = &sel;
+ if (mutation_sel == nullptr)
+ mutation_sel = &sel;
+ if (reproduction_sel == nullptr)
+ reproduction_sel = &sel;
+ program.generate_initial_population(program.get_typesystem().get_type().id());
+ program.setup_generational_evaluation(fitness_function_ref, *crossover_sel,
+ *mutation_sel, *reproduction_sel);
+ }
- void run_generation_loop()
- {
- BLT_DEBUG("Begin Generation Loop");
- while (!program.should_terminate())
- {
-#ifdef BLT_TRACK_ALLOCATIONS
+ void run_generation_loop()
+ {
+ BLT_DEBUG("Begin Generation Loop");
+ while (!program.should_terminate())
+ {
+ #ifdef BLT_TRACK_ALLOCATIONS
auto cross = crossover_calls.start_measurement();
auto mut = mutation_calls.start_measurement();
auto repo = reproduction_calls.start_measurement();
-#endif
- BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation());
- BLT_TRACE("Creating next generation");
- program.create_next_generation();
- BLT_TRACE("Move to next generation");
- program.next_generation();
- BLT_TRACE("Evaluate Fitness");
- program.evaluate_fitness();
- const auto& stats = program.get_population_stats();
- BLT_TRACE("Avg Fit: %lf, Best Fit: %lf, Worst Fit: %lf, Overall Fit: %lf",
- stats.average_fitness.load(std::memory_order_relaxed), stats.best_fitness.load(std::memory_order_relaxed),
- stats.worst_fitness.load(std::memory_order_relaxed), stats.overall_fitness.load(std::memory_order_relaxed));
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ BLT_TRACE("------------\\{Begin Generation {}}------------", program.get_current_generation());
+ BLT_TRACE("Creating next generation");
+ program.create_next_generation();
+ BLT_TRACE("Move to next generation");
+ program.next_generation();
+ BLT_TRACE("Evaluate Fitness");
+ program.evaluate_fitness();
+ const auto& stats = program.get_population_stats();
+ BLT_TRACE("Avg Fit: {:0.6f}, Best Fit: {:0.6f}, Worst Fit: {:0.6f}, Overall Fit: {:0.6f}", stats.average_fitness.load(std::memory_order_relaxed),
+ stats.best_fitness.load(std::memory_order_relaxed), stats.worst_fitness.load(std::memory_order_relaxed),
+ stats.overall_fitness.load(std::memory_order_relaxed));
+ #ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.stop_measurement(cross);
mutation_calls.stop_measurement(mut);
reproduction_calls.stop_measurement(repo);
const auto total = (cross.get_call_difference() * 2) + mut.get_call_difference() + repo.get_call_difference();
- BLT_TRACE("Calls Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total);
- BLT_TRACE("Value Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_value_difference(), mut.get_value_difference(), repo.get_value_difference(), (cross.get_value_difference() * 2 + mut.get_value_difference() + repo.get_value_difference()) - total);
-#endif
- BLT_TRACE("----------------------------------------------");
- std::cout << std::endl;
- }
- }
+ BLT_TRACE("Calls Crossover: {}, Mutation {}, Reproduction {}; {}", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total);
+ BLT_TRACE("Value Crossover: {}, Mutation {}, Reproduction {}; {}", cross.get_value_difference(), mut.get_value_difference(), repo.get_value_difference(), (cross.get_value_difference() * 2 + mut.get_value_difference() + repo.get_value_difference()) - total);
+ #endif
+ BLT_TRACE("----------------------------------------------");
+ std::cout << std::endl;
+ }
+ }
- auto get_and_print_best()
- {
- const auto best = program.get_best_individuals<3>();
+ auto get_and_print_best()
+ {
+ const auto best = program.get_best_individuals<3>();
- BLT_INFO("Best approximations:");
- for (auto& i_ref : best)
- {
- auto& i = i_ref.get();
- BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
- i.tree.print(std::cout);
- std::cout << "\n";
- }
+ BLT_INFO("Best approximations:");
+ for (auto& i_ref : best)
+ {
+ auto& i = i_ref.get();
+ BLT_DEBUG("Fitness: {:0.6f}, stand: {:0.6f}, raw: {:0.6f}", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
+ i.tree.print(std::cout);
+ std::cout << "\n";
+ }
- return best;
- }
+ return best;
+ }
- void print_stats() const
- {
- // TODO: make stats helper
- const auto& stats = program.get_population_stats();
- BLT_INFO("Stats:");
- BLT_INFO("Average fitness: %lf", stats.average_fitness.load());
- BLT_INFO("Best fitness: %lf", stats.best_fitness.load());
- BLT_INFO("Worst fitness: %lf", stats.worst_fitness.load());
- BLT_INFO("Overall fitness: %lf", stats.overall_fitness.load());
- }
+ void print_stats() const
+ {
+ // TODO: make stats helper
+ const auto& stats = program.get_population_stats();
+ BLT_INFO("Stats:");
+ BLT_INFO("Average fitness: {:0.6f}", stats.average_fitness.load());
+ BLT_INFO("Best fitness: {:0.6f}", stats.best_fitness.load());
+ BLT_INFO("Worst fitness: {:0.6f}", stats.worst_fitness.load());
+ BLT_INFO("Overall fitness: {:0.6f}", stats.overall_fitness.load());
+ }
- void execute()
- {
- setup_operations();
+ void execute()
+ {
+ setup_operations();
- generate_initial_population();
+ generate_initial_population();
- run_generation_loop();
+ run_generation_loop();
- get_and_print_best();
+ get_and_print_best();
- print_stats();
- }
+ print_stats();
+ }
- [[nodiscard]] const auto& get_training_cases() const
- {
- return training_cases;
- }
+ [[nodiscard]] const auto& get_training_cases() const
+ {
+ return training_cases;
+ }
- private:
- std::array training_cases{};
- };
+ private:
+ std::array training_cases{};
+ };
}
#endif //BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H
diff --git a/include/blt/gp/allocator.h b/include/blt/gp/allocator.h
index 5e9a3b2..15f78d8 100644
--- a/include/blt/gp/allocator.h
+++ b/include/blt/gp/allocator.h
@@ -20,7 +20,7 @@
#define BLT_GP_ALLOCATOR_H
#include
-#include
+#include
#include
#include
#include
diff --git a/include/blt/gp/operations.h b/include/blt/gp/operations.h
index c7f3d86..1fe2e69 100644
--- a/include/blt/gp/operations.h
+++ b/include/blt/gp/operations.h
@@ -63,9 +63,10 @@ namespace blt::gp
template
static void call_drop(stack_allocator& read_allocator, const size_t offset)
{
- if constexpr (blt::gp::detail::has_func_drop_v)
+ if constexpr (blt::gp::detail::has_func_drop_v>)
{
auto [type, ptr] = read_allocator.access_pointer>(offset);
+ // type is not ephemeral, so we must drop it.
if (!ptr.bit(0))
type.drop();
}
@@ -200,7 +201,7 @@ namespace blt::gp
[[nodiscard]] bool return_has_ephemeral_drop() const
{
- return detail::has_func_drop_v;
+ return detail::has_func_drop_v>;
}
operator_id id = -1;
diff --git a/include/blt/gp/program.h b/include/blt/gp/program.h
index 3a699e8..4183cc9 100644
--- a/include/blt/gp/program.h
+++ b/include/blt/gp/program.h
@@ -19,7 +19,6 @@
#ifndef BLT_GP_PROGRAM_H
#define BLT_GP_PROGRAM_H
-
#include
#include
#include
@@ -57,311 +56,309 @@
namespace blt::gp
{
- struct argc_t
- {
- blt::u32 argc = 0;
- blt::u32 argc_context = 0;
+ struct argc_t
+ {
+ blt::u32 argc = 0;
+ blt::u32 argc_context = 0;
- [[nodiscard]] bool is_terminal() const
- {
- return argc == 0;
- }
- };
+ [[nodiscard]] bool is_terminal() const
+ {
+ return argc == 0;
+ }
+ };
- struct operator_info_t
- {
- // types of the arguments
- tracked_vector argument_types;
- // return type of this operator
- type_id return_type;
- // number of arguments for this operator
- argc_t argc;
- // per operator function callable (slow)
- detail::operator_func_t func;
- };
+ struct operator_info_t
+ {
+ // types of the arguments
+ tracked_vector argument_types;
+ // return type of this operator
+ type_id return_type;
+ // number of arguments for this operator
+ argc_t argc;
+ // per operator function callable (slow)
+ detail::operator_func_t func;
+ };
- struct operator_metadata_t
- {
- blt::size_t arg_size_bytes = 0;
- blt::size_t return_size_bytes = 0;
- argc_t argc{};
- };
+ struct operator_metadata_t
+ {
+ blt::size_t arg_size_bytes = 0;
+ blt::size_t return_size_bytes = 0;
+ argc_t argc{};
+ };
- struct program_operator_storage_t
- {
- // indexed from return TYPE ID, returns index of operator
- expanding_buffer> terminals;
- expanding_buffer> non_terminals;
- expanding_buffer>> operators_ordered_terminals;
- // indexed from OPERATOR ID (operator number) to a bitfield of flags
- hashmap_t operator_flags;
+ struct program_operator_storage_t
+ {
+ // indexed from return TYPE ID, returns index of operator
+ expanding_buffer> terminals;
+ expanding_buffer> non_terminals;
+ expanding_buffer>> operators_ordered_terminals;
+ // indexed from OPERATOR ID (operator number) to a bitfield of flags
+ hashmap_t operator_flags;
- tracked_vector operators;
- tracked_vector operator_metadata;
- tracked_vector print_funcs;
- tracked_vector destroy_funcs;
- tracked_vector> names;
+ tracked_vector operators;
+ tracked_vector operator_metadata;
+ tracked_vector print_funcs;
+ tracked_vector destroy_funcs;
+ tracked_vector> names;
- detail::eval_func_t eval_func;
+ detail::eval_func_t eval_func;
- type_provider system;
- };
+ type_provider system;
+ };
- template
- class operator_builder
- {
- friend class gp_program;
+ template
+ class operator_builder
+ {
+ friend class gp_program;
- friend class blt::gp::detail::operator_storage_test;
+ friend class blt::gp::detail::operator_storage_test;
- public:
- explicit operator_builder() = default;
+ public:
+ explicit operator_builder() = default;
- template
- program_operator_storage_t& build(Operators&... operators)
- {
- blt::size_t largest_args = 0;
- blt::size_t largest_returns = 0;
- blt::u32 largest_argc = 0;
- operator_metadata_t meta;
- ((meta = add_operator(operators), largest_argc = std::max(meta.argc.argc, largest_argc),
- largest_args = std::max(meta.arg_size_bytes, largest_args), largest_returns = std::max(meta.return_size_bytes,
- largest_returns)), ...);
+ template
+ program_operator_storage_t& build(Operators&... operators)
+ {
+ blt::size_t largest_args = 0;
+ blt::size_t largest_returns = 0;
+ blt::u32 largest_argc = 0;
+ operator_metadata_t meta;
+ ((meta = add_operator(operators), largest_argc = std::max(meta.argc.argc, largest_argc), largest_args =
+ std::max(meta.arg_size_bytes, largest_args), largest_returns = std::max(meta.return_size_bytes, largest_returns)), ...);
- // largest = largest * largest_argc;
- size_t largest = largest_args * largest_argc * largest_returns * largest_argc;
+ // largest = largest * largest_argc;
+ size_t largest = largest_args * largest_argc * largest_returns * largest_argc;
- storage.eval_func = tree_t::make_execution_lambda(largest, operators...);
+ storage.eval_func = tree_t::make_execution_lambda(largest, operators...);
- blt::hashset_t has_terminals;
+ blt::hashset_t has_terminals;
- for (const auto& [index, value] : blt::enumerate(storage.terminals))
- {
- if (!value.empty())
- has_terminals.insert(index);
- }
+ for (const auto& [index, value] : blt::enumerate(storage.terminals))
+ {
+ if (!value.empty())
+ has_terminals.insert(index);
+ }
- for (const auto& [index, value] : blt::enumerate(storage.non_terminals))
- {
- if (value.empty())
- continue;
- auto return_type = index;
- tracked_vector> ordered_terminals;
- for (const auto& op : value)
- {
- // count number of terminals
- blt::size_t terminals = 0;
- for (const auto& type : storage.operators[op].argument_types)
- {
- if (has_terminals.contains(type))
- terminals++;
- }
- ordered_terminals.emplace_back(op, terminals);
- }
- bool found_terminal_inputs = false;
- bool matches_argc = false;
- for (const auto& terms : ordered_terminals)
- {
- if (terms.second == storage.operators[terms.first].argc.argc)
- matches_argc = true;
- if (terms.second != 0)
- found_terminal_inputs = true;
- if (matches_argc && found_terminal_inputs)
- break;
- }
- if (!found_terminal_inputs)
- BLT_ABORT(("Failed to find function with terminal arguments for return type " + std::to_string(return_type)).c_str());
- if (!matches_argc)
- {
- BLT_ABORT(("Failed to find a function which purely translates types "
- "(that is all input types are terminals) for return type " + std::to_string(return_type)).c_str());
- }
+ for (const auto& [index, value] : blt::enumerate(storage.non_terminals))
+ {
+ if (value.empty())
+ continue;
+ auto return_type = index;
+ tracked_vector> ordered_terminals;
+ for (const auto& op : value)
+ {
+ // count number of terminals
+ blt::size_t terminals = 0;
+ for (const auto& type : storage.operators[op].argument_types)
+ {
+ if (has_terminals.contains(type))
+ terminals++;
+ }
+ ordered_terminals.emplace_back(op, terminals);
+ }
+ bool found_terminal_inputs = false;
+ bool matches_argc = false;
+ for (const auto& terms : ordered_terminals)
+ {
+ if (terms.second == storage.operators[terms.first].argc.argc)
+ matches_argc = true;
+ if (terms.second != 0)
+ found_terminal_inputs = true;
+ if (matches_argc && found_terminal_inputs)
+ break;
+ }
+ if (!found_terminal_inputs)
+ BLT_ABORT(("Failed to find function with terminal arguments for return type " + std::to_string(return_type)).c_str());
+ if (!matches_argc)
+ {
+ BLT_ABORT(("Failed to find a function which purely translates types "
+ "(that is all input types are terminals) for return type " + std::to_string(return_type)).c_str());
+ }
- std::sort(ordered_terminals.begin(), ordered_terminals.end(), [](const auto& a, const auto& b)
- {
- return a.second > b.second;
- });
+ std::sort(ordered_terminals.begin(), ordered_terminals.end(), [](const auto& a, const auto& b) {
+ return a.second > b.second;
+ });
- auto first_size = *ordered_terminals.begin();
- auto iter = ordered_terminals.begin();
- while (++iter != ordered_terminals.end() && iter->second == first_size.second)
- {
- }
+ auto first_size = *ordered_terminals.begin();
+ auto iter = ordered_terminals.begin();
+ while (++iter != ordered_terminals.end() && iter->second == first_size.second)
+ {}
- ordered_terminals.erase(iter, ordered_terminals.end());
+ ordered_terminals.erase(iter, ordered_terminals.end());
- storage.operators_ordered_terminals[return_type] = ordered_terminals;
- }
+ storage.operators_ordered_terminals[return_type] = ordered_terminals;
+ }
- return storage;
- }
+ return storage;
+ }
- program_operator_storage_t&& grab()
- {
- return std::move(storage);
- }
+ program_operator_storage_t&& grab()
+ {
+ return std::move(storage);
+ }
- private:
- template
- auto add_operator(operation_t& op)
- {
- // check for types we can register
- (storage.system.register_type(), ...);
- storage.system.register_type();
+ private:
+ template
+ auto add_operator(operation_t& op)
+ {
+ // check for types we can register
+ (storage.system.register_type(), ...);
+ storage.system.register_type();
- auto return_type_id = storage.system.get_type().id();
- auto operator_id = blt::gp::operator_id(storage.operators.size());
- op.id = operator_id;
+ auto return_type_id = storage.system.get_type().id();
+ auto operator_id = blt::gp::operator_id(storage.operators.size());
+ op.id = operator_id;
- operator_info_t info;
+ operator_info_t info;
- if constexpr (sizeof...(Args) > 0)
- {
- (add_non_context_argument>(info.argument_types), ...);
- }
+ if constexpr (sizeof...(Args) > 0)
+ {
+ (add_non_context_argument>(info.argument_types), ...);
+ }
- info.argc.argc_context = info.argc.argc = sizeof...(Args);
- info.return_type = return_type_id;
- info.func = op.template make_callable();
+ info.argc.argc_context = info.argc.argc = sizeof...(Args);
+ info.return_type = return_type_id;
+ info.func = op.template make_callable();
- ((std::is_same_v, Context> ? info.argc.argc -= 1 : 0), ...);
+ ((std::is_same_v, Context> ? info.argc.argc -= 1 : 0), ...);
- auto& operator_list = info.argc.argc == 0 ? storage.terminals : storage.non_terminals;
- operator_list[return_type_id].push_back(operator_id);
+ auto& operator_list = info.argc.argc == 0 ? storage.terminals : storage.non_terminals;
+ operator_list[return_type_id].push_back(operator_id);
- BLT_ASSERT(info.argc.argc_context - info.argc.argc <= 1 && "Cannot pass multiple context as arguments!");
+ BLT_ASSERT(info.argc.argc_context - info.argc.argc <= 1 && "Cannot pass multiple context as arguments!");
- storage.operators.push_back(info);
+ storage.operators.push_back(info);
- operator_metadata_t meta;
- if constexpr (sizeof...(Args) != 0)
- {
- meta.arg_size_bytes = (stack_allocator::aligned_size() + ...);
- }
- meta.return_size_bytes = stack_allocator::aligned_size();
- meta.argc = info.argc;
+ operator_metadata_t meta;
+ if constexpr (sizeof...(Args) != 0)
+ {
+ meta.arg_size_bytes = (stack_allocator::aligned_size() + ...);
+ }
+ meta.return_size_bytes = stack_allocator::aligned_size();
+ meta.argc = info.argc;
- storage.operator_metadata.push_back(meta);
- storage.print_funcs.push_back([&op](std::ostream& out, stack_allocator& stack)
- {
- if constexpr (blt::meta::is_streamable_v)
- {
- out << stack.from(0);
- (void)(op); // remove warning
- }
- else
- {
- out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]";
- }
- });
- storage.destroy_funcs.push_back([](const detail::destroy_t type, u8* data)
- {
- switch (type)
- {
- case detail::destroy_t::PTR:
- case detail::destroy_t::RETURN:
- if constexpr (detail::has_func_drop_v>)
- {
- reinterpret_cast*>(data)->drop();
- }
- break;
- }
- });
- storage.names.push_back(op.get_name());
- storage.operator_flags.emplace(operator_id, operator_special_flags{op.is_ephemeral(), op.return_has_ephemeral_drop()});
- return meta;
- }
+ storage.operator_metadata.push_back(meta);
+ storage.print_funcs.push_back([&op](std::ostream& out, stack_allocator& stack) {
+ if constexpr (blt::meta::is_streamable_v)
+ {
+ out << stack.from(0);
+ (void) (op); // remove warning
+ } else
+ {
+ out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]";
+ }
+ });
+ storage.destroy_funcs.push_back([](const detail::destroy_t type, u8* data) {
+ switch (type)
+ {
+ case detail::destroy_t::PTR:
+ case detail::destroy_t::RETURN:
+ if constexpr (detail::has_func_drop_v>)
+ {
+ reinterpret_cast*>(data)->drop();
+ }
+ break;
+ }
+ });
+ storage.names.push_back(op.get_name());
+ storage.operator_flags.emplace(operator_id, operator_special_flags{op.is_ephemeral(), op.return_has_ephemeral_drop()});
+ return meta;
+ }
- template
- void add_non_context_argument(decltype(operator_info_t::argument_types)& types)
- {
- if constexpr (!std::is_same_v>)
- {
- types.push_back(storage.system.get_type().id());
- }
- }
+ template
+ void add_non_context_argument(decltype(operator_info_t::argument_types)& types)
+ {
+ if constexpr (!std::is_same_v>)
+ {
+ types.push_back(storage.system.get_type().id());
+ }
+ }
- private:
- program_operator_storage_t storage;
- };
+ private:
+ program_operator_storage_t storage;
+ };
- class gp_program
- {
- public:
- /**
- * 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
- *
- * @param seed
- */
- explicit gp_program(blt::u64 seed): seed_func([seed] { return seed; })
- {
- create_threads();
- selection_probabilities.update(config);
- }
+ class gp_program
+ {
+ public:
+ /**
+ * 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
+ *
+ * @param seed
+ */
+ explicit gp_program(blt::u64 seed): seed_func([seed] {
+ return seed;
+ })
+ {
+ create_threads();
+ set_config(config);
+ }
- explicit gp_program(blt::u64 seed, const prog_config_t& config): seed_func([seed] { return seed; }), config(config)
- {
- create_threads();
- selection_probabilities.update(config);
- }
+ explicit gp_program(blt::u64 seed, const prog_config_t& config): seed_func([seed] {
+ return seed;
+ })
+ {
+ create_threads();
+ set_config(config);
+ }
- /**
- *
- * @param seed_func Function which provides a new random seed every time it is called.
- * This will be used by each thread to initialize a new random number generator
- */
- explicit gp_program(std::function seed_func): seed_func(std::move(seed_func))
- {
- create_threads();
- selection_probabilities.update(config);
- }
+ /**
+ *
+ * @param seed_func Function which provides a new random seed every time it is called.
+ * This will be used by each thread to initialize a new random number generator
+ */
+ explicit gp_program(std::function seed_func): seed_func(std::move(seed_func))
+ {
+ create_threads();
+ set_config(config);
+ }
- explicit gp_program(std::function seed_func, const prog_config_t& config): seed_func(std::move(seed_func)), config(config)
- {
- create_threads();
- selection_probabilities.update(config);
- }
+ explicit gp_program(std::function seed_func, const prog_config_t& config): seed_func(std::move(seed_func))
+ {
+ create_threads();
+ set_config(config);
+ }
- ~gp_program()
- {
- thread_helper.lifetime_over = true;
- thread_helper.barrier.notify_all();
- thread_helper.thread_function_condition.notify_all();
- for (auto& thread : thread_helper.threads)
- {
- if (thread->joinable())
- thread->join();
- }
- }
+ ~gp_program()
+ {
+ thread_helper.lifetime_over = true;
+ thread_helper.barrier.notify_all();
+ thread_helper.thread_function_condition.notify_all();
+ for (auto& thread : thread_helper.threads)
+ {
+ if (thread->joinable())
+ thread->join();
+ }
+ }
- void create_next_generation()
- {
-#ifdef BLT_TRACK_ALLOCATIONS
+ void create_next_generation()
+ {
+ #ifdef BLT_TRACK_ALLOCATIONS
auto gen_alloc = blt::gp::tracker.start_measurement();
-#endif
- // should already be empty
- thread_helper.next_gen_left.store(config.population_size, std::memory_order_release);
- (*thread_execution_service)(0);
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ // should already be empty
+ thread_helper.next_gen_left.store(selection_probabilities.replacement_amount.value_or(config.population_size), std::memory_order_release);
+ (*thread_execution_service)(0);
+ #ifdef BLT_TRACK_ALLOCATIONS
blt::gp::tracker.stop_measurement(gen_alloc);
gen_alloc.pretty_print("Generation");
-#endif
- }
+ #endif
+ }
- void next_generation()
- {
- std::swap(current_pop, next_pop);
- ++current_generation;
- }
+ void next_generation()
+ {
+ std::swap(current_pop, next_pop);
+ ++current_generation;
+ }
- void evaluate_fitness()
- {
-#ifdef BLT_TRACK_ALLOCATIONS
+ void evaluate_fitness()
+ {
+ #ifdef BLT_TRACK_ALLOCATIONS
auto fitness_alloc = blt::gp::tracker.start_measurement();
-#endif
- evaluate_fitness_internal();
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ evaluate_fitness_internal();
+ #ifdef BLT_TRACK_ALLOCATIONS
blt::gp::tracker.stop_measurement(fitness_alloc);
fitness_alloc.pretty_print("Fitness");
evaluation_calls.call();
@@ -370,430 +367,558 @@ namespace blt::gp
{
evaluation_allocations.call(fitness_alloc.getAllocatedByteDifference());
}
-#endif
- }
+ #endif
+ }
- void reset_program(type_id root_type, bool eval_fitness_now = true)
- {
- current_generation = 0;
- current_pop = config.pop_initializer.get().generate(
- {*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size});
- next_pop = population_t(current_pop);
- BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
- ("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
- BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
- ("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
- if (eval_fitness_now)
- evaluate_fitness_internal();
- }
+ void reset_program(type_id root_type, bool eval_fitness_now = true)
+ {
+ current_generation = 0;
+ current_pop = config.pop_initializer.get().generate({
+ *this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size
+ });
+ next_pop = population_t(current_pop);
+ BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
+ ("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
+ BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
+ ("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
+ if (eval_fitness_now)
+ evaluate_fitness_internal();
+ }
- void kill()
- {
- thread_helper.lifetime_over = true;
- }
+ void kill()
+ {
+ thread_helper.lifetime_over = true;
+ }
- /**
- * takes in a reference to a function for the fitness evaluation function (must return a value convertable to double)
- * The lambda must accept a tree for evaluation, and an index (current tree)
- *
- * tree_t& current_tree, blt::size_t index_of_tree
- *
- * Container must be concurrently accessible from multiple threads using operator[]
- *
- * NOTE: 0 is considered the best, in terms of standardized fitness
- */
- template
- void generate_population(type_id root_type, FitnessFunc& fitness_function,
- Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
- bool eval_fitness_now = true)
- {
- using LambdaReturn = std::invoke_result_t;
- current_pop = config.pop_initializer.get().generate(
- {*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size});
- next_pop = population_t(current_pop);
- BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
- ("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
- BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
- ("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
- if (config.threads == 1)
- {
- BLT_INFO("Starting with single thread variant!");
- thread_execution_service = std::unique_ptr>(new std::function(
- [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t)
- {
- if (thread_helper.evaluation_left > 0)
- {
- current_stats.normalized_fitness.clear();
- double sum_of_prob = 0;
- for (const auto& [index, ind] : blt::enumerate(current_pop.get_individuals()))
- {
- ind.fitness = {};
- if constexpr (std::is_same_v || std::is_convertible_v)
- {
- if (fitness_function(ind.tree, ind.fitness, index))
- fitness_should_exit = true;
- }
- else
- fitness_function(ind.tree, ind.fitness, index);
+ void generate_initial_population(const type_id root_type)
+ {
+ current_pop = config.pop_initializer.get().generate({
+ *this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size
+ });
+ next_pop = population_t(current_pop);
+ BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
+ ("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
+ BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
+ ("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
+ }
- if (ind.fitness.adjusted_fitness > current_stats.best_fitness)
- current_stats.best_fitness = ind.fitness.adjusted_fitness;
+ /**
+ * takes in a reference to a function for the fitness evaluation function (must return a value convertable to double)
+ * The lambda must accept a tree for evaluation, and an index (current tree)
+ *
+ * tree_t& current_tree, blt::size_t index_of_tree
+ *
+ * Container must be concurrently accessible from multiple threads using operator[]
+ *
+ * NOTE: the larger the adjusted fitness, the better.
+ */
+ template
+ void setup_generational_evaluation(FitnessFunc& fitness_function, Crossover& crossover_selection, Mutation& mutation_selection,
+ Reproduction& reproduction_selection, bool eval_fitness_now = true)
+ {
+ if (config.threads == 1)
+ {
+ BLT_INFO("Starting generational with single thread variant!");
+ thread_execution_service = std::unique_ptr>(new std::function(
+ [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t) {
+ single_threaded_fitness_eval()(fitness_function);
- if (ind.fitness.adjusted_fitness < current_stats.worst_fitness)
- current_stats.worst_fitness = ind.fitness.adjusted_fitness;
+ if (thread_helper.next_gen_left > 0)
+ {
+ current_stats.normalized_fitness.clear();
+ double sum_of_prob = 0;
+ for (const auto& ind : current_pop)
+ {
+ const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
+ current_stats.normalized_fitness.push_back(sum_of_prob + prob);
+ sum_of_prob += prob;
+ }
- current_stats.overall_fitness = current_stats.overall_fitness + ind.fitness.adjusted_fitness;
- }
- for (auto& ind : current_pop)
- {
- auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
- current_stats.normalized_fitness.push_back(sum_of_prob + prob);
- sum_of_prob += prob;
- }
- thread_helper.evaluation_left = 0;
- }
- if (thread_helper.next_gen_left > 0)
- {
- auto args = get_selector_args();
+ const auto args = get_selector_args();
- crossover_selection.pre_process(*this, current_pop);
- mutation_selection.pre_process(*this, current_pop);
- reproduction_selection.pre_process(*this, current_pop);
+ crossover_selection.pre_process(*this, current_pop);
+ mutation_selection.pre_process(*this, current_pop);
+ reproduction_selection.pre_process(*this, current_pop);
- size_t start = detail::perform_elitism(args, next_pop);
+ size_t start = detail::perform_elitism(args, next_pop);
- while (start < config.population_size)
- {
- tree_t& c1 = next_pop.get_individuals()[start].tree;
- tree_t* c2 = nullptr;
- if (start + 1 < config.population_size)
- c2 = &next_pop.get_individuals()[start + 1].tree;
- start += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
- }
+ while (start < config.population_size)
+ {
+ tree_t& c1 = next_pop.get_individuals()[start].tree;
+ tree_t* c2 = nullptr;
+ if (start + 1 < config.population_size)
+ c2 = &next_pop.get_individuals()[start + 1].tree;
+ start += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
+ }
- thread_helper.next_gen_left = 0;
- }
- }));
- }
- else
- {
- BLT_INFO("Starting thread execution service!");
- std::scoped_lock lock(thread_helper.thread_function_control);
- thread_execution_service = std::unique_ptr>(new std::function(
- [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](size_t id)
- {
- thread_helper.barrier.wait();
+ thread_helper.next_gen_left = 0;
+ }
+ }));
+ } else
+ {
+ BLT_INFO("Starting generational thread execution service!");
+ std::scoped_lock lock(thread_helper.thread_function_control);
+ thread_execution_service = std::unique_ptr>(new std::function(
+ [this, &fitness_function, &crossover_selection, &mutation_selection, &reproduction_selection](const size_t id) {
+ thread_helper.barrier.wait();
- if (thread_helper.evaluation_left > 0)
- {
- while (thread_helper.evaluation_left > 0)
- {
- blt::size_t size = 0;
- blt::size_t begin = 0;
- blt::size_t end = thread_helper.evaluation_left.load(std::memory_order_relaxed);
- do
- {
- size = std::min(end, config.evaluation_size);
- begin = end - size;
- }
- while (!thread_helper.evaluation_left.compare_exchange_weak(end, end - size,
- std::memory_order::memory_order_relaxed,
- std::memory_order::memory_order_relaxed));
- for (blt::size_t i = begin; i < end; i++)
- {
- auto& ind = current_pop.get_individuals()[i];
+ multi_threaded_fitness_eval()(fitness_function, id);
- ind.fitness = {};
- if constexpr (std::is_same_v || std::is_convertible_v)
- {
- auto result = fitness_function(ind.tree, ind.fitness, i);
- if (result)
- fitness_should_exit = true;
- }
- else
- {
- fitness_function(ind.tree, ind.fitness, i);
- }
+ if (thread_helper.next_gen_left > 0)
+ {
+ thread_helper.barrier.wait();
+ auto args = get_selector_args();
+ if (id == 0)
+ {
+ current_stats.normalized_fitness.clear();
+ double sum_of_prob = 0;
+ for (const auto& ind : current_pop)
+ {
+ const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
+ current_stats.normalized_fitness.push_back(sum_of_prob + prob);
+ sum_of_prob += prob;
+ }
- auto old_best = current_stats.best_fitness.load(std::memory_order_relaxed);
- while (ind.fitness.adjusted_fitness > old_best &&
- !current_stats.best_fitness.compare_exchange_weak(old_best, ind.fitness.adjusted_fitness,
- std::memory_order_relaxed,
- std::memory_order_relaxed))
- {
- }
+ crossover_selection.pre_process(*this, current_pop);
+ if (&crossover_selection != &mutation_selection)
+ mutation_selection.pre_process(*this, current_pop);
+ if (&crossover_selection != &reproduction_selection)
+ reproduction_selection.pre_process(*this, current_pop);
+ const auto elite_amount = detail::perform_elitism(args, next_pop);
+ thread_helper.next_gen_left -= elite_amount;
+ }
+ thread_helper.barrier.wait();
- auto old_worst = current_stats.worst_fitness.load(std::memory_order_relaxed);
- while (ind.fitness.adjusted_fitness < old_worst &&
- !current_stats.worst_fitness.compare_exchange_weak(old_worst, ind.fitness.adjusted_fitness,
- std::memory_order_relaxed,
- std::memory_order_relaxed))
- {
- }
+ while (thread_helper.next_gen_left > 0)
+ {
+ size_t size = 0;
+ size_t begin = 0;
+ size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed);
+ do
+ {
+ size = std::min(end, config.evaluation_size);
+ begin = end - size;
+ } while (!thread_helper.next_gen_left.compare_exchange_weak(
+ end, end - size, std::memory_order::memory_order_relaxed, std::memory_order::memory_order_relaxed));
- auto old_overall = current_stats.overall_fitness.load(std::memory_order_relaxed);
- while (!current_stats.overall_fitness.compare_exchange_weak(old_overall,
- ind.fitness.adjusted_fitness + old_overall,
- std::memory_order_relaxed,
- std::memory_order_relaxed))
- {
- }
- }
- }
- }
- if (thread_helper.next_gen_left > 0)
- {
- thread_helper.barrier.wait();
- auto args = get_selector_args();
- if (id == 0)
- {
- current_stats.normalized_fitness.clear();
- double sum_of_prob = 0;
- for (auto& ind : current_pop)
- {
- auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
- current_stats.normalized_fitness.push_back(sum_of_prob + prob);
- sum_of_prob += prob;
- }
+ while (begin != end)
+ {
+ auto index = config.elites + begin;
+ tree_t& c1 = next_pop.get_individuals()[index].tree;
+ tree_t* c2 = nullptr;
+ if (begin + 1 < end)
+ c2 = &next_pop.get_individuals()[index + 1].tree;
+ begin += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
+ }
+ }
+ }
+ thread_helper.barrier.wait();
+ }));
+ thread_helper.thread_function_condition.notify_all();
+ }
+ if (eval_fitness_now)
+ evaluate_fitness_internal();
+ }
- crossover_selection.pre_process(*this, current_pop);
- if (&crossover_selection != &mutation_selection)
- mutation_selection.pre_process(*this, current_pop);
- if (&crossover_selection != &reproduction_selection)
- reproduction_selection.pre_process(*this, current_pop);
- const auto elite_amount = detail::perform_elitism(args, next_pop);
- thread_helper.next_gen_left -= elite_amount;
- }
- thread_helper.barrier.wait();
+ template
+ void setup_steady_state_evaluation(FitnessFunc& fitness_function, SelectionStrat& replacement_strategy, size_t replacement_amount,
+ Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
+ const bool eval_fitness_now = true)
+ {
+ selection_probabilities.replacement_amount = replacement_amount;
+ if (config.threads == 1)
+ {
+ BLT_INFO("Starting steady state with single thread variant!");
+ thread_execution_service = std::unique_ptr>(new std::function(
+ [this, &fitness_function, &replacement_strategy, &crossover_selection, &mutation_selection, &reproduction_selection](size_t) {
+ single_threaded_fitness_eval()(fitness_function);
- while (thread_helper.next_gen_left > 0)
- {
- size_t size = 0;
- size_t begin = 0;
- size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed);
- do
- {
- size = std::min(end, config.evaluation_size);
- begin = end - size;
- }
- while (!thread_helper.next_gen_left.compare_exchange_weak(end, end - size,
- std::memory_order::memory_order_relaxed,
- std::memory_order::memory_order_relaxed));
+ if (thread_helper.next_gen_left > 0)
+ {
+ current_stats.normalized_fitness.clear();
+ double sum_of_prob = 0;
+ for (const auto& ind : current_pop)
+ {
+ const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
+ current_stats.normalized_fitness.push_back(sum_of_prob + prob);
+ sum_of_prob += prob;
+ }
- while (begin != end)
- {
- auto index = config.elites + begin;
- tree_t& c1 = next_pop.get_individuals()[index].tree;
- tree_t* c2 = nullptr;
- if (begin + 1 < end)
- c2 = &next_pop.get_individuals()[index + 1].tree;
- begin += perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
- }
- }
- }
- thread_helper.barrier.wait();
- }));
- thread_helper.thread_function_condition.notify_all();
- }
- if (eval_fitness_now)
- evaluate_fitness_internal();
- }
+ next_pop = population_t(current_pop);
- [[nodiscard]] bool should_terminate() const
- {
- return current_generation >= config.max_generations || fitness_should_exit;
- }
+ replacement_strategy.pre_process(*this, next_pop);
+ crossover_selection.pre_process(*this, current_pop);
+ mutation_selection.pre_process(*this, current_pop);
+ reproduction_selection.pre_process(*this, current_pop);
- [[nodiscard]] bool should_thread_terminate() const
- {
- return thread_helper.lifetime_over;
- }
+ while (thread_helper.next_gen_left > 0)
+ {
+ tree_t& c1 = replacement_strategy.select(*this, next_pop);
+ tree_t* c2 = nullptr;
+ if (thread_helper.next_gen_left > 1)
+ while (c2 != &c1)
+ c2 = &replacement_strategy.select(*this, next_pop);
+ thread_helper.next_gen_left -= perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1,
+ c2);
+ }
- operator_id select_terminal(type_id id)
- {
- // we wanted a terminal, but could not find one, so we will select from a function that has a terminal
- if (storage.terminals[id].empty())
- return select_non_terminal_too_deep(id);
- return get_random().select(storage.terminals[id]);
- }
+ thread_helper.next_gen_left = 0;
+ }
+ }));
+ } else
+ {
+ BLT_INFO("Starting steady state thread execution service!");
+ std::scoped_lock lock(thread_helper.thread_function_control);
+ thread_execution_service = std::unique_ptr>(new std::function(
+ [this, &fitness_function, &replacement_strategy, &crossover_selection, &mutation_selection, &reproduction_selection](
+ const size_t id) {
+ thread_helper.barrier.wait();
- operator_id select_non_terminal(type_id id)
- {
- // non-terminal doesn't exist, return a terminal. This is useful for types that are defined only to have a random value, nothing more.
- // was considering an std::optional<> but that would complicate the generator code considerably. I'll mark this as a TODO for v2
- if (storage.non_terminals[id].empty())
- return select_terminal(id);
- return get_random().select(storage.non_terminals[id]);
- }
+ multi_threaded_fitness_eval()(fitness_function, id);
- operator_id select_non_terminal_too_deep(type_id id)
- {
- // this should probably be an error.
- if (storage.operators_ordered_terminals[id].empty())
- BLT_ABORT("An impossible state has been reached. Please consult the manual. Error 43");
- return get_random().select(storage.operators_ordered_terminals[id]).first;
- }
+ if (thread_helper.next_gen_left > 0)
+ {
+ thread_helper.barrier.wait();
+ if (id == 0)
+ {
+ current_stats.normalized_fitness.clear();
+ double sum_of_prob = 0;
+ for (const auto& ind : current_pop)
+ {
+ const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
+ current_stats.normalized_fitness.push_back(sum_of_prob + prob);
+ sum_of_prob += prob;
+ }
- auto& get_current_pop()
- {
- return current_pop;
- }
+ current_pop = population_t(next_pop);
- [[nodiscard]] random_t& get_random() const;
+ replacement_strategy.pre_process(*this, next_pop);
+ crossover_selection.pre_process(*this, current_pop);
+ if (&crossover_selection != &mutation_selection)
+ mutation_selection.pre_process(*this, current_pop);
+ if (&crossover_selection != &reproduction_selection)
+ reproduction_selection.pre_process(*this, current_pop);
+ }
+ thread_helper.barrier.wait();
- [[nodiscard]] const prog_config_t& get_config() const
- {
- return config;
- }
+ while (thread_helper.next_gen_left > 0)
+ {
+ size_t size = 0;
+ size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed);
+ do
+ {
+ size = std::min(end, config.evaluation_size);
+ } while (!thread_helper.next_gen_left.compare_exchange_weak(
+ end, end - size, std::memory_order::memory_order_relaxed, std::memory_order::memory_order_relaxed));
- [[nodiscard]] type_provider& get_typesystem()
- {
- return storage.system;
- }
+ while (size > 0)
+ {
+ tree_t& c1 = replacement_strategy.select(*this, next_pop);
+ tree_t* c2 = nullptr;
+ if (thread_helper.next_gen_left > 1)
+ while (c2 != &c1)
+ c2 = &replacement_strategy.select(*this, next_pop);
+ size -= perform_selection(crossover_selection, mutation_selection, reproduction_selection, c1, c2);
+ }
+ }
+ }
+ thread_helper.barrier.wait();
+ }));
+ thread_helper.thread_function_condition.notify_all();
+ }
+ if (eval_fitness_now)
+ evaluate_fitness_internal();
+ }
- [[nodiscard]] operator_info_t& get_operator_info(operator_id id)
- {
- return storage.operators[id];
- }
+ [[nodiscard]] bool should_terminate() const
+ {
+ return current_generation >= config.max_generations || fitness_should_exit;
+ }
- [[nodiscard]] detail::print_func_t& get_print_func(operator_id id)
- {
- return storage.print_funcs[id];
- }
+ [[nodiscard]] bool should_thread_terminate() const
+ {
+ return thread_helper.lifetime_over;
+ }
- [[nodiscard]] detail::destroy_func_t& get_destroy_func(operator_id id)
- {
- return storage.destroy_funcs[id];
- }
+ operator_id select_terminal(type_id id)
+ {
+ // we wanted a terminal, but could not find one, so we will select from a function that has a terminal
+ if (storage.terminals[id].empty())
+ return select_non_terminal_too_deep(id);
+ return get_random().select(storage.terminals[id]);
+ }
- [[nodiscard]] std::optional get_name(operator_id id)
- {
- return storage.names[id];
- }
+ operator_id select_non_terminal(type_id id)
+ {
+ // non-terminal doesn't exist, return a terminal. This is useful for types that are defined only to have a random value, nothing more.
+ // was considering an std::optional<> but that would complicate the generator code considerably. I'll mark this as a TODO for v2
+ if (storage.non_terminals[id].empty())
+ return select_terminal(id);
+ return get_random().select(storage.non_terminals[id]);
+ }
- [[nodiscard]] tracked_vector& get_type_terminals(type_id id)
- {
- return storage.terminals[id];
- }
+ operator_id select_non_terminal_too_deep(type_id id)
+ {
+ // this should probably be an error.
+ if (storage.operators_ordered_terminals[id].empty())
+ BLT_ABORT("An impossible state has been reached. Please consult the manual. Error 43");
+ return get_random().select(storage.operators_ordered_terminals[id]).first;
+ }
- [[nodiscard]] tracked_vector& get_type_non_terminals(type_id id)
- {
- return storage.non_terminals[id];
- }
+ auto& get_current_pop()
+ {
+ return current_pop;
+ }
- [[nodiscard]] detail::eval_func_t& get_eval_func()
- {
- return storage.eval_func;
- }
+ [[nodiscard]] random_t& get_random() const;
- [[nodiscard]] auto get_current_generation() const
- {
- return current_generation.load();
- }
+ [[nodiscard]] const prog_config_t& get_config() const
+ {
+ return config;
+ }
- [[nodiscard]] const auto& get_population_stats() const
- {
- return current_stats;
- }
+ void set_config(const prog_config_t& config)
+ {
+ this->config = config;
+ selection_probabilities.update(this->config);
+ }
- [[nodiscard]] bool is_operator_ephemeral(const operator_id id) const
- {
- return storage.operator_flags.find(static_cast(id))->second.is_ephemeral();
- }
+ [[nodiscard]] type_provider& get_typesystem()
+ {
+ return storage.system;
+ }
- [[nodiscard]] bool operator_has_ephemeral_drop(const operator_id id) const
- {
- return storage.operator_flags.find(static_cast(id))->second.has_ephemeral_drop();
- }
+ [[nodiscard]] operator_info_t& get_operator_info(operator_id id)
+ {
+ return storage.operators[id];
+ }
- [[nodiscard]] operator_special_flags get_operator_flags(const operator_id id) const
- {
- return storage.operator_flags.find(static_cast(id))->second;
- }
+ [[nodiscard]] detail::print_func_t& get_print_func(operator_id id)
+ {
+ return storage.print_funcs[id];
+ }
- void set_operations(program_operator_storage_t op)
- {
- storage = std::move(op);
- }
+ [[nodiscard]] detail::destroy_func_t& get_destroy_func(operator_id id)
+ {
+ return storage.destroy_funcs[id];
+ }
- template
- std::array get_best_indexes()
- {
- std::array arr;
+ [[nodiscard]] std::optional get_name(operator_id id) const
+ {
+ return storage.names[id];
+ }
- tracked_vector> values;
- values.reserve(current_pop.get_individuals().size());
+ [[nodiscard]] tracked_vector& get_type_terminals(type_id id)
+ {
+ return storage.terminals[id];
+ }
- for (const auto& [index, value] : blt::enumerate(current_pop.get_individuals()))
- values.emplace_back(index, value.fitness.adjusted_fitness);
+ [[nodiscard]] tracked_vector& get_type_non_terminals(type_id id)
+ {
+ return storage.non_terminals[id];
+ }
- std::sort(values.begin(), values.end(), [](const auto& a, const auto& b)
- {
- return a.second > b.second;
- });
+ [[nodiscard]] detail::eval_func_t& get_eval_func()
+ {
+ return storage.eval_func;
+ }
- for (blt::size_t i = 0; i < std::min(size, config.population_size); i++)
- arr[i] = values[i].first;
- for (blt::size_t i = std::min(size, config.population_size); i < size; i++)
- arr[i] = 0;
+ [[nodiscard]] auto get_current_generation() const
+ {
+ return current_generation.load();
+ }
- return arr;
- }
+ [[nodiscard]] const auto& get_population_stats() const
+ {
+ return current_stats;
+ }
- template
- auto get_best_trees()
- {
- return convert_array, size>>(get_best_indexes(),
- [this](auto&& arr, blt::size_t index) -> tree_t& {
- return current_pop.get_individuals()[arr[index]].tree;
- },
- std::make_integer_sequence());
- }
+ [[nodiscard]] bool is_operator_ephemeral(const operator_id id) const
+ {
+ return storage.operator_flags.find(static_cast(id))->second.is_ephemeral();
+ }
- template
- auto get_best_individuals()
- {
- return convert_array, size>>(get_best_indexes(),
- [this](auto&& arr, blt::size_t index) -> individual_t& {
- return current_pop.get_individuals()[arr[index]];
- },
- std::make_integer_sequence());
- }
+ [[nodiscard]] bool operator_has_ephemeral_drop(const operator_id id) const
+ {
+ return storage.operator_flags.find(static_cast(id))->second.has_ephemeral_drop();
+ }
- private:
- template
- size_t perform_selection(Crossover& crossover, Mutation& mutation, Reproduction& reproduction, tree_t& c1, tree_t* c2)
- {
- if (get_random().choice(selection_probabilities.crossover_chance))
- {
- auto ptr = c2;
- if (ptr == nullptr)
- ptr = &tree_t::get_thread_local(*this);
-#ifdef BLT_TRACK_ALLOCATIONS
+ [[nodiscard]] operator_special_flags get_operator_flags(const operator_id id) const
+ {
+ return storage.operator_flags.find(static_cast(id))->second;
+ }
+
+ void set_operations(program_operator_storage_t op)
+ {
+ storage = std::move(op);
+ }
+
+ template
+ std::array get_best_indexes()
+ {
+ std::array arr;
+
+ tracked_vector> values;
+ values.reserve(current_pop.get_individuals().size());
+
+ for (const auto& [index, value] : blt::enumerate(current_pop.get_individuals()))
+ values.emplace_back(index, value.fitness.adjusted_fitness);
+
+ std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) {
+ return a.second > b.second;
+ });
+
+ for (blt::size_t i = 0; i < std::min(size, config.population_size); i++)
+ arr[i] = values[i].first;
+ for (blt::size_t i = std::min(size, config.population_size); i < size; i++)
+ arr[i] = 0;
+
+ return arr;
+ }
+
+ template
+ auto get_best_trees()
+ {
+ return convert_array, size>>(get_best_indexes(),
+ [this](auto&& arr, blt::size_t index) -> tree_t& {
+ return current_pop.get_individuals()[arr[index]].tree;
+ }, std::make_integer_sequence());
+ }
+
+ template
+ auto get_best_individuals()
+ {
+ return convert_array, size>>(get_best_indexes(),
+ [this](auto&& arr, blt::size_t index) -> individual_t& {
+ return current_pop.get_individuals()[arr[index]];
+ }, std::make_integer_sequence());
+ }
+
+ private:
+ template
+ auto single_threaded_fitness_eval()
+ {
+ return [this](FitnessFunc& fitness_function) {
+ if (thread_helper.evaluation_left > 0)
+ {
+ current_stats.normalized_fitness.clear();
+ double sum_of_prob = 0;
+ perform_fitness_function(0, current_pop.get_individuals().size(), fitness_function);
+ for (const auto& ind : current_pop)
+ {
+ const auto prob = (ind.fitness.adjusted_fitness / current_stats.overall_fitness);
+ current_stats.normalized_fitness.push_back(sum_of_prob + prob);
+ sum_of_prob += prob;
+ }
+ std::sort(current_pop.begin(), current_pop.end(), [](const auto& a, const auto& b)
+ {
+ return a.fitness.adjusted_fitness > b.fitness.adjusted_fitness;
+ });
+ thread_helper.evaluation_left = 0;
+ }
+ };
+ }
+
+ template
+ auto multi_threaded_fitness_eval()
+ {
+ return [this](FitnessFunc& fitness_function, size_t thread_id) {
+ if (thread_helper.evaluation_left > 0)
+ {
+ thread_helper.barrier.wait();
+ while (thread_helper.evaluation_left > 0)
+ {
+ size_t size = 0;
+ size_t begin = 0;
+ size_t end = thread_helper.evaluation_left.load(std::memory_order_relaxed);
+ do
+ {
+ size = std::min(end, config.evaluation_size);
+ begin = end - size;
+ } while (!thread_helper.evaluation_left.compare_exchange_weak(end, end - size, std::memory_order::memory_order_relaxed,
+ std::memory_order::memory_order_relaxed));
+ perform_fitness_function(begin, end, fitness_function);
+ }
+ thread_helper.barrier.wait();
+ if (thread_id == 0)
+ {
+ std::sort(current_pop.begin(), current_pop.end(), [](const auto& a, const auto& b)
+ {
+ return a.fitness.adjusted_fitness > b.fitness.adjusted_fitness;
+ });
+ }
+ thread_helper.barrier.wait();
+ }
+ };
+ }
+
+ template
+ void perform_fitness_function(const size_t begin, const size_t end, FitnessFunction& fitness_function)
+ {
+ using LambdaReturn = std::invoke_result_t;
+ for (size_t i = begin; i < end; i++)
+ {
+ auto& ind = current_pop.get_individuals()[i];
+
+ ind.fitness = {};
+ if constexpr (std::is_same_v || std::is_convertible_v)
+ {
+ if (fitness_function(ind.tree, ind.fitness, i))
+ fitness_should_exit = true;
+ } else
+ {
+ fitness_function(ind.tree, ind.fitness, i);
+ }
+
+ auto old_best = current_stats.best_fitness.load(std::memory_order_relaxed);
+ while (ind.fitness.adjusted_fitness > old_best && !current_stats.best_fitness.compare_exchange_weak(
+ old_best, ind.fitness.adjusted_fitness, std::memory_order_relaxed, std::memory_order_relaxed))
+ {}
+
+ auto old_worst = current_stats.worst_fitness.load(std::memory_order_relaxed);
+ while (ind.fitness.adjusted_fitness < old_worst && !current_stats.worst_fitness.compare_exchange_weak(
+ old_worst, ind.fitness.adjusted_fitness, std::memory_order_relaxed, std::memory_order_relaxed))
+ {}
+
+ auto old_overall = current_stats.overall_fitness.load(std::memory_order_relaxed);
+ while (!current_stats.overall_fitness.compare_exchange_weak(old_overall, ind.fitness.adjusted_fitness + old_overall,
+ std::memory_order_relaxed, std::memory_order_relaxed))
+ {}
+ }
+ }
+
+ template
+ size_t perform_selection(Crossover& crossover, Mutation& mutation, Reproduction& reproduction, tree_t& c1, tree_t* c2)
+ {
+ if (get_random().choice(selection_probabilities.crossover_chance))
+ {
+ auto ptr = c2;
+ if (ptr == nullptr)
+ ptr = &tree_t::get_thread_local(*this);
+ #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
-#endif
- const tree_t* p1;
- const tree_t* p2;
- size_t runs = 0;
- do
- {
- // BLT_TRACE("%lu %p %p", runs, &c1, &tree);
- p1 = &crossover.select(*this, current_pop);
- p2 = &crossover.select(*this, current_pop);
- // BLT_TRACE("%p %p || %lu", p1, p2, current_pop.get_individuals().size());
+ #endif
+ const tree_t* p1;
+ const tree_t* p2;
+ size_t runs = 0;
+ do
+ {
+ // BLT_TRACE("%lu %p %p", runs, &c1, &tree);
+ p1 = &crossover.select(*this, current_pop);
+ p2 = &crossover.select(*this, current_pop);
+ // BLT_TRACE("%p %p || %lu", p1, p2, current_pop.get_individuals().size());
- c1.copy_fast(*p1);
- ptr->copy_fast(*p2);
- // ptr->copy_fast(*p2);
+ c1.copy_fast(*p1);
+ ptr->copy_fast(*p2);
+ // ptr->copy_fast(*p2);
- if (++runs >= config.crossover.get().get_config().max_crossover_iterations)
- return 0;
-#ifdef BLT_TRACK_ALLOCATIONS
+ if (++runs >= config.crossover.get().get_config().max_crossover_iterations)
+ return 0;
+ #ifdef BLT_TRACK_ALLOCATIONS
crossover_calls.value(1);
-#endif
- }
- while (!config.crossover.get().apply(*this, *p1, *p2, c1, *ptr));
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ } while (!config.crossover.get().apply(*this, *p1, *p2, c1, *ptr));
+ #ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
crossover_calls.call();
if (state.getAllocatedByteDifference() != 0)
@@ -801,31 +926,30 @@ namespace blt::gp
crossover_allocations.call(state.getAllocatedByteDifference());
crossover_allocations.set_value(std::max(crossover_allocations.get_value(), state.getAllocatedByteDifference()));
}
-#endif
- if (c2 == nullptr)
- {
- tree_t::get_thread_local(*this).clear(*this);
- return 1;
- }
- return 2;
- }
- if (get_random().choice(selection_probabilities.mutation_chance))
- {
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ if (c2 == nullptr)
+ {
+ tree_t::get_thread_local(*this).clear(*this);
+ return 1;
+ }
+ return 2;
+ }
+ if (get_random().choice(selection_probabilities.mutation_chance))
+ {
+ #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
-#endif
- // mutation
- const tree_t* p;
- do
- {
- p = &mutation.select(*this, current_pop);
- c1.copy_fast(*p);
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ // mutation
+ const tree_t* p;
+ do
+ {
+ p = &mutation.select(*this, current_pop);
+ c1.copy_fast(*p);
+ #ifdef BLT_TRACK_ALLOCATIONS
mutation_calls.value(1);
-#endif
- }
- while (!config.mutator.get().apply(*this, *p, c1));
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ } while (!config.mutator.get().apply(*this, *p, c1));
+ #ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
mutation_calls.call();
if (state.getAllocationDifference() != 0)
@@ -833,17 +957,17 @@ namespace blt::gp
mutation_allocations.call(state.getAllocatedByteDifference());
mutation_allocations.set_value(std::max(mutation_allocations.get_value(), state.getAllocatedByteDifference()));
}
-#endif
- return 1;
- }
- if (selection_probabilities.reproduction_chance > 0)
- {
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ return 1;
+ }
+ if (selection_probabilities.reproduction_chance > 0)
+ {
+ #ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
-#endif
- // reproduction
- c1.copy_fast(reproduction.select(*this, current_pop));
-#ifdef BLT_TRACK_ALLOCATIONS
+ #endif
+ // reproduction
+ c1.copy_fast(reproduction.select(*this, current_pop));
+ #ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
reproduction_calls.call();
reproduction_calls.value(1);
@@ -852,88 +976,88 @@ namespace blt::gp
reproduction_allocations.call(state.getAllocatedByteDifference());
reproduction_allocations.set_value(std::max(reproduction_allocations.get_value(), state.getAllocatedByteDifference()));
}
-#endif
- return 1;
- }
+ #endif
+ return 1;
+ }
- return 0;
- }
+ return 0;
+ }
- selector_args get_selector_args()
- {
- return {*this, current_pop, current_stats, config, get_random()};
- }
+ selector_args get_selector_args()
+ {
+ return {*this, current_pop, current_stats, config, get_random()};
+ }
- template
- Return convert_array(std::array&& arr, Accessor&& accessor,
- std::integer_sequence)
- {
- return Return{accessor(arr, indexes)...};
- }
+ template
+ Return convert_array(std::array&& arr, Accessor&& accessor, std::integer_sequence)
+ {
+ return Return{accessor(arr, indexes)...};
+ }
- void create_threads();
+ void create_threads();
- void evaluate_fitness_internal()
- {
- statistic_history.push_back(current_stats);
- current_stats.clear();
- thread_helper.evaluation_left.store(config.population_size, std::memory_order_release);
- (*thread_execution_service)(0);
+ void evaluate_fitness_internal()
+ {
+ statistic_history.push_back(current_stats);
+ current_stats.clear();
+ thread_helper.evaluation_left.store(config.population_size, std::memory_order_release);
+ (*thread_execution_service)(0);
- current_stats.average_fitness = current_stats.overall_fitness / static_cast(config.population_size);
- }
+ current_stats.average_fitness = current_stats.overall_fitness / static_cast(config.population_size);
+ }
- private:
- program_operator_storage_t storage;
- std::function seed_func;
- prog_config_t config{};
+ private:
+ program_operator_storage_t storage;
+ std::function seed_func;
+ prog_config_t config{};
- // internal cache which stores already calculated probability values
- struct
- {
- double crossover_chance = 0;
- double mutation_chance = 0;
- double reproduction_chance = 0;
+ // internal cache which stores already calculated probability values
+ struct
+ {
+ double crossover_chance = 0;
+ double mutation_chance = 0;
+ double reproduction_chance = 0;
- void update(const prog_config_t& config)
- {
- const auto total = config.crossover_chance + config.mutation_chance + config.reproduction_chance;
- crossover_chance = config.crossover_chance / total;
- mutation_chance = config.mutation_chance / total;
- reproduction_chance = config.reproduction_chance / total;
- }
- } selection_probabilities;
+ std::optional replacement_amount;
- population_t current_pop;
- population_t next_pop;
+ void update(const prog_config_t& config)
+ {
+ const auto total = config.crossover_chance + config.mutation_chance + config.reproduction_chance;
+ crossover_chance = config.crossover_chance / total;
+ mutation_chance = config.mutation_chance / total;
+ reproduction_chance = config.reproduction_chance / total;
+ }
+ } selection_probabilities;
- std::atomic_uint64_t current_generation = 0;
+ population_t current_pop;
+ population_t next_pop;
- std::atomic_bool fitness_should_exit = false;
+ std::atomic_uint64_t current_generation = 0;
- population_stats current_stats{};
- tracked_vector statistic_history;
+ std::atomic_bool fitness_should_exit = false;
- struct concurrency_storage
- {
- std::vector> threads;
+ population_stats current_stats{};
+ tracked_vector statistic_history;
- std::mutex thread_function_control{};
- std::condition_variable thread_function_condition{};
+ struct concurrency_storage
+ {
+ std::vector> threads;
- std::atomic_uint64_t evaluation_left = 0;
- std::atomic_uint64_t next_gen_left = 0;
+ std::mutex thread_function_control{};
+ std::condition_variable thread_function_condition{};
- std::atomic_bool lifetime_over = false;
- blt::barrier barrier;
+ std::atomic_uint64_t evaluation_left = 0;
+ std::atomic_uint64_t next_gen_left = 0;
- explicit concurrency_storage(blt::size_t threads): barrier(threads, lifetime_over)
- {
- }
- } thread_helper{config.threads == 0 ? std::thread::hardware_concurrency() : config.threads};
+ std::atomic_bool lifetime_over = false;
+ blt::barrier barrier;
- std::unique_ptr> thread_execution_service = nullptr;
- };
+ explicit concurrency_storage(blt::size_t threads): barrier(threads, lifetime_over)
+ {}
+ } thread_helper{config.threads == 0 ? std::thread::hardware_concurrency() : config.threads};
+
+ std::unique_ptr> thread_execution_service = nullptr;
+ };
}
#endif //BLT_GP_PROGRAM_H
diff --git a/include/blt/gp/selection.h b/include/blt/gp/selection.h
index 460535b..2ad45f9 100644
--- a/include/blt/gp/selection.h
+++ b/include/blt/gp/selection.h
@@ -61,16 +61,16 @@ namespace blt::gp
{
for (size_t i = 0; i < config.elites; i++)
{
- if (ind.value.fitness.adjusted_fitness >= values[i].second)
+ if (ind.second.fitness.adjusted_fitness >= values[i].second)
{
bool doesnt_contain = true;
for (blt::size_t j = 0; j < config.elites; j++)
{
- if (ind.index == values[j].first)
+ if (ind.first == values[j].first)
doesnt_contain = false;
}
if (doesnt_contain)
- values[i] = {ind.index, ind.value.fitness.adjusted_fitness};
+ values[i] = {ind.first, ind.second.fitness.adjusted_fitness};
break;
}
}
diff --git a/include/blt/gp/stack.h b/include/blt/gp/stack.h
index ef046cc..a83457c 100644
--- a/include/blt/gp/stack.h
+++ b/include/blt/gp/stack.h
@@ -21,21 +21,16 @@
#include
#include
-#include
-#include
+#include
#include
-#include
#include
-#include
-#include
+#include
#include
#include
-#include
#include
#include
#include
#include
-#include
namespace blt::gp
{
@@ -87,7 +82,7 @@ namespace blt::gp
static constexpr size_t aligned_size() noexcept
{
const auto bytes = detail::aligned_size(sizeof(NO_REF_T));
- if constexpr (blt::gp::detail::has_func_drop_v)
+ if constexpr (blt::gp::detail::has_func_drop_v>)
return bytes + detail::aligned_size(sizeof(std::atomic_uint64_t*));
return bytes;
}
@@ -179,7 +174,7 @@ namespace blt::gp
const auto ptr = static_cast(allocate_bytes_for_size(aligned_size()));
std::memcpy(ptr, &t, sizeof(NO_REF));
- if constexpr (gp::detail::has_func_drop_v)
+ if constexpr (gp::detail::has_func_drop_v>)
{
new(ptr + sizeof(NO_REF)) mem::pointer_storage{nullptr};
}
diff --git a/include/blt/gp/tree.h b/include/blt/gp/tree.h
index 2d10a1e..18e75be 100644
--- a/include/blt/gp/tree.h
+++ b/include/blt/gp/tree.h
@@ -166,7 +166,7 @@ namespace blt::gp
~evaluation_ref()
{
- if constexpr (detail::has_func_drop_v)
+ if constexpr (detail::has_func_drop_v>)
{
if (m_value.get() != nullptr)
{
diff --git a/include/blt/gp/typesystem.h b/include/blt/gp/typesystem.h
index 517d8cd..b74edda 100644
--- a/include/blt/gp/typesystem.h
+++ b/include/blt/gp/typesystem.h
@@ -48,7 +48,7 @@ namespace blt::gp
template
static type make_type(const type_id id)
{
- return type(stack_allocator::aligned_size(), id, blt::type_string(), detail::has_func_drop_v);
+ return type(stack_allocator::aligned_size(), id, blt::type_string(), detail::has_func_drop_v>);
}
[[nodiscard]] size_t size() const
diff --git a/src/generators.cpp b/src/generators.cpp
index 718398a..e089688 100644
--- a/src/generators.cpp
+++ b/src/generators.cpp
@@ -17,7 +17,7 @@
*/
#include
#include
-#include
+#include
#include
namespace blt::gp
diff --git a/src/program.cpp b/src/program.cpp
index 07f8467..1cee290 100644
--- a/src/program.cpp
+++ b/src/program.cpp
@@ -26,36 +26,36 @@ namespace blt::gp
static advanced_mutation_t s_mutator;
static crossover_t s_crossover;
static ramped_half_initializer_t s_init;
-
+
prog_config_t::prog_config_t(): mutator(s_mutator), crossover(s_crossover), pop_initializer(s_init)
{
-
+
}
-
+
prog_config_t::prog_config_t(const std::reference_wrapper& popInitializer):
mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer)
{}
-
+
prog_config_t::prog_config_t(size_t populationSize, const std::reference_wrapper& popInitializer):
population_size(populationSize), mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer)
{}
-
+
prog_config_t::prog_config_t(size_t populationSize):
population_size(populationSize), mutator(s_mutator), crossover(s_crossover), pop_initializer(s_init)
{}
-
+
random_t& gp_program::get_random() const
{
thread_local static blt::gp::random_t random_engine{seed_func()};
return random_engine;
}
-
+
stack_allocator::Allocator& stack_allocator::get_allocator()
{
static Allocator allocator;
return allocator;
}
-
+
void gp_program::create_threads()
{
#ifdef BLT_TRACK_ALLOCATIONS
diff --git a/src/selection.cpp b/src/selection.cpp
index 14377e0..b2174df 100644
--- a/src/selection.cpp
+++ b/src/selection.cpp
@@ -20,12 +20,12 @@
namespace blt::gp
{
- void select_best_t::pre_process(gp_program&, population_t& pop)
+ void select_best_t::pre_process(gp_program&, population_t&)
{
- std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
- {
- return a.fitness.adjusted_fitness > b.fitness.adjusted_fitness;
- });
+ // std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
+ // {
+ // return a.fitness.adjusted_fitness > b.fitness.adjusted_fitness;
+ // });
index = 0;
}
@@ -35,19 +35,19 @@ namespace blt::gp
return pop.get_individuals()[index.fetch_add(1, std::memory_order_relaxed) % size].tree;
}
- void select_worst_t::pre_process(gp_program&, population_t& pop)
+ void select_worst_t::pre_process(gp_program&, population_t&)
{
- std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
- {
- return a.fitness.adjusted_fitness < b.fitness.adjusted_fitness;
- });
+ // std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
+ // {
+ // return a.fitness.adjusted_fitness < b.fitness.adjusted_fitness;
+ // });
index = 0;
}
const tree_t& select_worst_t::select(gp_program&, const population_t& pop)
{
const auto size = pop.get_individuals().size();
- return pop.get_individuals()[index.fetch_add(1, std::memory_order_relaxed) % size].tree;
+ return pop.get_individuals()[(size - 1) - (index.fetch_add(1, std::memory_order_relaxed) % size)].tree;
}
const tree_t& select_random_t::select(gp_program& program, const population_t& pop)
diff --git a/src/transformers.cpp b/src/transformers.cpp
index de7c1e7..2b9221d 100644
--- a/src/transformers.cpp
+++ b/src/transformers.cpp
@@ -145,15 +145,34 @@ namespace blt::gp
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);
+#if BLT_DEBUG_LEVEL >= 2
+ auto previous_size = new_tree.size();
+ auto previous_bytes = new_tree.total_value_bytes();
+#endif
config.generator.get().generate(new_tree, {program, node.type, config.replacement_min_depth, config.replacement_max_depth});
+#if BLT_DEBUG_LEVEL >= 2
+ const auto old_op = c.get_operator(node.pos);
+ if (!new_tree.check(detail::debug::context_ptr))
+ {
+ BLT_ERROR("Mutate point new tree check failed!");
+ BLT_ERROR("Old Op: {} got replaced with New Op: {}", program.get_name(old_op.id()).value_or("Unknown"),
+ program.get_name(new_tree.get_operator(0).id()).value_or("Unknown"));
+ BLT_ERROR("Tree started with size: {} and bytes: {}", previous_size, previous_bytes);
+ throw std::runtime_error("Mutate Point tree check failed");
+ }
+#endif
+
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
+ const auto new_op = c.get_operator(node.pos);
if (!c.check(detail::debug::context_ptr))
{
print_mutate_stats();
+ BLT_ERROR("Old Op: {} got replaced with New Op: {}", program.get_name(old_op.id()).value_or("Unknown"),
+ program.get_name(new_op.id()).value_or("Unknown"));
throw std::runtime_error("Mutate Point tree check failed");
}
#endif
diff --git a/src/tree.cpp b/src/tree.cpp
index f45a6a7..7a45418 100644
--- a/src/tree.cpp
+++ b/src/tree.cpp
@@ -18,7 +18,7 @@
#include
#include
#include
-#include
+#include
#include
#include
@@ -501,6 +501,7 @@ namespace blt::gp
tree_t& tree_t::get_thread_local(gp_program& program)
{
thread_local tree_t tree{program};
+ tree.clear(program);
return tree;
}
@@ -544,8 +545,8 @@ namespace blt::gp
if (bytes_expected != bytes_size)
{
- BLT_ERROR_STREAM << "Stack state: " << values.size() << "\n";
- BLT_ERROR("Child tree bytes %ld vs expected %ld, difference: %ld", bytes_size, bytes_expected,
+ BLT_ERROR("Stack state: {}", values.size());
+ BLT_ERROR("Child tree bytes {} vs expected {}, difference: {}", bytes_size, bytes_expected,
static_cast(bytes_expected) - static_cast(bytes_size));
BLT_ERROR("Amount of bytes in stack doesn't match the number of bytes expected for the operations");
return false;
@@ -588,18 +589,18 @@ namespace blt::gp
if (v1 != v2)
{
const auto vd = std::abs(v1 - v2);
- BLT_ERROR("found %ld bytes expected %ld bytes, total difference: %ld", v1, v2, vd);
- BLT_ERROR("Total Produced %ld || Total Consumed %ld || Total Difference %ld", total_produced, total_consumed,
+ BLT_ERROR("found {} bytes expected {} bytes, total difference: {}", v1, v2, vd);
+ BLT_ERROR("Total Produced {} || Total Consumed {} || Total Difference {}", total_produced, total_consumed,
std::abs(static_cast(total_produced) - static_cast(total_consumed)));
return false;
}
}
catch (std::exception& e)
{
- BLT_ERROR("Exception occurred \"%s\"", e.what());
- BLT_ERROR("Total Produced %ld || Total Consumed %ld || Total Difference %ld", total_produced, total_consumed,
+ BLT_ERROR("Exception occurred \"{}\"", e.what());
+ BLT_ERROR("Total Produced {} || Total Consumed {} || Total Difference {}", total_produced, total_consumed,
std::abs(static_cast(total_produced) - static_cast(total_consumed)));
- BLT_ERROR("We failed at index %lu", index);
+ BLT_ERROR("We failed at index {}", index);
return false;
}
return true;
diff --git a/src/util/trackers.cpp b/src/util/trackers.cpp
index c548c55..c986d29 100644
--- a/src/util/trackers.cpp
+++ b/src/util/trackers.cpp
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
#include
-#include
+#include
#include "blt/format/format.h"
#include
diff --git a/tests/2_type_drop_test.cpp b/tests/2_type_drop_test.cpp
new file mode 100644
index 0000000..69b0bf9
--- /dev/null
+++ b/tests/2_type_drop_test.cpp
@@ -0,0 +1,194 @@
+/*
+ *
+ * Copyright (C) 2025 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 "../examples/symbolic_regression.h"
+#include
+#include
+
+using namespace blt::gp;
+
+std::atomic_uint64_t normal_construct = 0;
+std::atomic_uint64_t ephemeral_construct = 0;
+std::atomic_uint64_t normal_drop = 0;
+std::atomic_uint64_t ephemeral_drop = 0;
+std::atomic_uint64_t max_allocated = 0;
+
+struct drop_type
+{
+ float* m_value;
+ bool ephemeral = false;
+
+ drop_type() : m_value(new float(0))
+ {
+ ++normal_construct;
+ }
+
+ explicit drop_type(const float silly) : m_value(new float(silly))
+ {
+ ++normal_construct;
+ }
+
+ explicit drop_type(const float silly, bool) : m_value(new float(silly)), ephemeral(true)
+ {
+ // BLT_TRACE("Constructor with value %f", silly);
+ ++ephemeral_construct;
+ }
+
+ [[nodiscard]] float value() const
+ {
+ return *m_value;
+ }
+
+ void drop() const
+ {
+ if (ephemeral)
+ {
+ std::cout << ("Ephemeral drop") << std::endl;
+ ++ephemeral_drop;
+ }
+ else
+ ++normal_drop;
+ delete m_value;
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const drop_type& dt)
+ {
+ os << dt.m_value;
+ return os;
+ }
+};
+
+struct context
+{
+ float x, y;
+};
+
+prog_config_t config = prog_config_t()
+ .set_initial_min_tree_size(2)
+ .set_initial_max_tree_size(6)
+ .set_elite_count(2)
+ .set_crossover_chance(0.8)
+ .set_mutation_chance(0.1)
+ .set_reproduction_chance(0.1)
+ .set_max_generations(50)
+ .set_pop_size(500)
+ .set_thread_count(1);
+
+
+example::symbolic_regression_t regression{691ul, config};
+
+operation_t add{[](const drop_type a, const drop_type b) { return drop_type{a.value() + b.value()}; }, "add"};
+operation_t addf{[](const float a, const float b) { return a + b; }, "addf"};
+operation_t sub([](const drop_type a, const drop_type b) { return drop_type{a.value() - b.value()}; }, "sub");
+operation_t subf([](const float a, const float b) { return a - b; }, "subf");
+operation_t mul([](const drop_type a, const drop_type b) { return drop_type{a.value() * b.value()}; }, "mul");
+operation_t mulf([](const float a, const float b) { return a * b; }, "mulf");
+operation_t pro_div([](const drop_type a, const drop_type b) { return drop_type{b.value() == 0.0f ? 0.0f : a.value() / b.value()}; }, "div");
+operation_t pro_divf([](const float a, const float b) { return b == 0.0f ? 0.0f : a / b; }, "divf");
+operation_t op_sin([](const drop_type a) { return drop_type{std::sin(a.value())}; }, "sin");
+operation_t op_sinf([](const float a) { return std::sin(a); }, "sinf");
+operation_t op_cos([](const drop_type a) { return drop_type{std::cos(a.value())}; }, "cos");
+operation_t op_cosf([](const float a) { return std::cos(a); }, "cosf");
+operation_t op_exp([](const drop_type a) { return drop_type{std::exp(a.value())}; }, "exp");
+operation_t op_expf([](const float a) { return std::exp(a); }, "expf");
+operation_t op_log([](const drop_type a) { return drop_type{a.value() <= 0.0f ? 0.0f : std::log(a.value())}; }, "log");
+operation_t op_logf([](const float a) { return a <= 0.0f ? 0.0f : std::log(a); }, "logf");
+operation_t op_tof([](const drop_type a) { return a.value(); }, "to_f");
+operation_t op_todrop([](const float a) { return drop_type{a}; }, "to_drop");
+operation_t op_mixed_input([](const drop_type a, const float f)
+{
+ return a.value() + f;
+}, "mixed_input");
+auto lit = operation_t([]()
+{
+ return drop_type{regression.get_program().get_random().get_float(-1.0f, 1.0f), true};
+}, "lit").set_ephemeral();
+
+auto litf = operation_t([]()
+{
+ return regression.get_program().get_random().get_float(-1.0f, 1.0f);
+}, "litf").set_ephemeral();
+
+operation_t op_x([](const context& context)
+{
+ return drop_type{context.x};
+}, "x");
+
+operation_t op_xf([](const context& context)
+{
+ return context.x;
+}, "xf");
+
+bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t)
+{
+ if (normal_construct - normal_drop > max_allocated)
+ max_allocated = normal_construct - normal_drop;
+ constexpr static double value_cutoff = 1.e15;
+ for (auto& fitness_case : regression.get_training_cases())
+ {
+ BLT_GP_UPDATE_CONTEXT(fitness_case);
+ auto val = current_tree.get_evaluation_ref(fitness_case);
+ const auto diff = std::abs(fitness_case.y - val.get().value());
+ if (diff < value_cutoff)
+ {
+ fitness.raw_fitness += diff;
+ if (diff <= 0.01)
+ fitness.hits++;
+ }
+ else
+ fitness.raw_fitness += value_cutoff;
+ }
+ fitness.standardized_fitness = fitness.raw_fitness;
+ fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness));
+ return static_cast(fitness.hits) == regression.get_training_cases().size();
+}
+
+int main()
+{
+ operator_builder builder{};
+ builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, op_mixed_input, lit, op_x, addf, subf, mulf, pro_divf, op_sinf, op_cosf,
+ op_expf, op_logf,
+ litf, op_xf, op_tof, op_todrop);
+ regression.get_program().set_operations(builder.grab());
+
+ auto& program = regression.get_program();
+ static auto sel = select_tournament_t{};
+ program.generate_initial_population(program.get_typesystem().get_type().id());
+ program.setup_generational_evaluation(fitness_function, sel, sel, sel);
+ while (!program.should_terminate())
+ {
+ BLT_TRACE("---------------\\{Begin Generation {}}---------------", program.get_current_generation());
+ BLT_TRACE("Creating next generation");
+ program.create_next_generation();
+ BLT_TRACE("Move to next generation");
+ program.next_generation();
+ BLT_TRACE("Evaluate Fitness");
+ program.evaluate_fitness();
+ }
+
+ // program.get_best_individuals<1>()[0].get().tree.print(program, std::cout, true, true);
+
+ regression.get_program().get_current_pop().clear();
+ regression.get_program().next_generation();
+ regression.get_program().get_current_pop().clear();
+
+ BLT_TRACE("Created {} times", normal_construct.load());
+ BLT_TRACE("Dropped {} times", normal_drop.load());
+ BLT_TRACE("Ephemeral created {} times", ephemeral_construct.load());
+ BLT_TRACE("Ephemeral dropped {} times", ephemeral_drop.load());
+ BLT_TRACE("Max allocated {} times", max_allocated.load());
+}
diff --git a/tests/drop_test.cpp b/tests/drop_test.cpp
index 1513b1f..509a7fb 100644
--- a/tests/drop_test.cpp
+++ b/tests/drop_test.cpp
@@ -17,7 +17,7 @@
*/
#include "../examples/symbolic_regression.h"
#include
-#include
+#include
using namespace blt::gp;
@@ -140,10 +140,11 @@ int main()
auto& program = regression.get_program();
static auto sel = select_tournament_t{};
- program.generate_population(program.get_typesystem().get_type().id(), fitness_function, sel, sel, sel);
+ program.generate_initial_population(program.get_typesystem().get_type().id());
+ program.setup_generational_evaluation(fitness_function, sel, sel, sel);
while (!program.should_terminate())
{
- BLT_TRACE("---------------{Begin Generation %lu}---------------", program.get_current_generation());
+ BLT_TRACE("---------------\\{Begin Generation {}}---------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
@@ -158,10 +159,10 @@ int main()
regression.get_program().next_generation();
regression.get_program().get_current_pop().clear();
- BLT_TRACE("Created %ld times", normal_construct.load());
- BLT_TRACE("Dropped %ld times", normal_drop.load());
- BLT_TRACE("Ephemeral created %ld times", ephemeral_construct.load());
- BLT_TRACE("Ephemeral dropped %ld times", ephemeral_drop.load());
- BLT_TRACE("Max allocated %ld times", max_allocated.load());
+ BLT_TRACE("Created {} times", normal_construct.load());
+ BLT_TRACE("Dropped {} times", normal_drop.load());
+ BLT_TRACE("Ephemeral created {} times", ephemeral_construct.load());
+ BLT_TRACE("Ephemeral dropped {} times", ephemeral_drop.load());
+ BLT_TRACE("Max allocated {} times", max_allocated.load());
}
diff --git a/tests/symbolic_regression_test.cpp b/tests/symbolic_regression_test.cpp
index 74219da..d7239ef 100644
--- a/tests/symbolic_regression_test.cpp
+++ b/tests/symbolic_regression_test.cpp
@@ -19,6 +19,7 @@
#include
#include
+#include