selection, add lambdas to operators, few tests, mutation

thread
Brett 2024-07-08 22:20:51 -04:00
parent f37582ab4a
commit f983c0fb62
17 changed files with 856 additions and 58 deletions

View File

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="gp progject" pattern="file[blt-gp]:include//*||file[blt-gp]:examples//*||file[blt-gp]:src//*" />
</component>

View File

@ -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 ()

View File

@ -78,7 +78,7 @@ int main()
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type<float>().id(), 500, 3, 10});
for (auto& tree : pop.getIndividuals())
for (auto& tree : pop.for_each_tree())
{
auto value = tree.get_evaluation_value<float>(nullptr);

View File

@ -116,7 +116,7 @@ int main()
BLT_INFO("Pre-Crossover:");
for (auto& tree : pop.getIndividuals())
{
auto f = tree.get_evaluation_value<float>(nullptr);
auto f = tree.tree.get_evaluation_value<float>(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<float>(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<float>(nullptr);
pos.push_back(f);

143
examples/gp_test_6.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
* <Short Description>
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* <Short Description>
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/gp/program.h>
#include <blt/gp/generators.h>
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
#include <blt/gp/transformers.h>
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<bool>(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<float> dist(-32000, 32000);
static std::uniform_real_distribution<float> 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<float>();
type_system.register_type<bool>();
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<float>().id(), 500, 3, 10});
blt::gp::population_t new_pop;
blt::gp::mutation_t mutator;
blt::gp::grow_generator_t generator;
std::vector<float> pre;
std::vector<float> pos;
BLT_INFO("Pre-Mutation:");
for (auto& tree : pop.for_each_tree())
{
auto f = tree.get_evaluation_value<float>(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<float>(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;
}

126
examples/gp_test_7.cpp Normal file
View File

@ -0,0 +1,126 @@
/*
* <Short Description>
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/gp/program.h>
#include <blt/gp/generators.h>
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
#include <blt/gp/transformers.h>
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<bool>(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<float> dist(-32000, 32000);
static std::uniform_real_distribution<float> 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<float>();
type_system.register_type<bool>();
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<float>().id(), 500, 3, 10});
blt::gp::population_t new_pop;
blt::gp::mutation_t mutator;
blt::gp::grow_generator_t generator;
std::vector<float> pre;
std::vector<float> pos;
BLT_INFO("Pre-Mutation:");
for (auto& tree : pop.for_each_tree())
{
auto f = tree.get_evaluation_value<float>(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<float>(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;
}

View File

@ -23,6 +23,7 @@
#include <blt/std/logging.h>
#include <blt/std/types.h>
#include <ostream>
#include <optional>
namespace blt::gp
{
@ -55,7 +56,7 @@ namespace blt::gp
// context*, read stack, write stack
using callable_t = std::function<void(void*, stack_allocator&, stack_allocator&)>;
// to, from
using transfer_t = std::function<void(stack_allocator&, stack_allocator&)>;
using transfer_t = std::function<void(std::optional<std::reference_wrapper<stack_allocator>>, stack_allocator&)>;
// debug function,
using print_func_t = std::function<void(std::ostream&, stack_allocator&)>;
}

View File

@ -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

View File

@ -107,14 +107,14 @@ namespace blt::gp
using call_with<Return, Args...>::call_with;
};
template<typename>
template<typename, typename>
class operation_t;
template<typename Return, typename... Args>
class operation_t<Return(Args...)>
template<typename ArgType, typename Return, typename... Args>
class operation_t<ArgType, Return(Args...)>
{
public:
using function_t = std::function<Return(Args...)>;
using function_t = ArgType;
constexpr operation_t(const operation_t& copy) = default;
@ -183,24 +183,24 @@ namespace blt::gp
std::optional<std::string_view> name;
};
template<typename Return, typename Class, typename... Args>
class operation_t<Return (Class::*)(Args...) const> : public operation_t<Return(Args...)>
template<typename ArgType, typename Return, typename Class, typename... Args>
class operation_t<ArgType, Return (Class::*)(Args...) const> : public operation_t<ArgType, Return(Args...)>
{
public:
using operation_t<Return(Args...)>::operation_t;
using operation_t<ArgType, Return(Args...)>::operation_t;
};
template<typename Lambda>
operation_t(Lambda) -> operation_t<decltype(&Lambda::operator())>;
operation_t(Lambda) -> operation_t<Lambda, decltype(&Lambda::operator())>;
template<typename Return, typename... Args>
operation_t(Return(*)(Args...)) -> operation_t<Return(Args...)>;
operation_t(Return(*)(Args...)) -> operation_t<Return(*)(Args...), Return(Args...)>;
template<typename Lambda>
operation_t(Lambda, std::optional<std::string_view>) -> operation_t<decltype(&Lambda::operator())>;
operation_t(Lambda, std::optional<std::string_view>) -> operation_t<Lambda, decltype(&Lambda::operator())>;
template<typename Return, typename... Args>
operation_t(Return(*)(Args...), std::optional<std::string_view>) -> operation_t<Return(Args...)>;
operation_t(Return(*)(Args...), std::optional<std::string_view>) -> operation_t<Return(*)(Args...), Return(Args...)>;
// templat\e<typename Return, typename Class, typename... Args>
// operation_t<Return(Args...)> make_operator(Return (Class::*)(Args...) const lambda)

View File

@ -29,6 +29,7 @@
#include <iostream>
#include <random>
#include <algorithm>
#include <memory>
#include <blt/std/ranges.h>
#include <blt/std/hashmap.h>
@ -38,6 +39,7 @@
#include <blt/gp/fwdecl.h>
#include <blt/gp/typesystem.h>
#include <blt/gp/operations.h>
#include <blt/gp/transformers.h>
#include <blt/gp/tree.h>
#include <blt/gp/stack.h>
@ -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<std::vector<std::pair<operator_id, blt::size_t>>> operators_ordered_terminals;
// indexed from OPERATOR ID (operator number)
blt::hashset_t<operator_id> static_types;
// blt::expanding_buffer<std::vector<type>> argument_types;
// blt::expanding_buffer<argc_t> operator_argc;
// std::vector<detail::callable_t> operators;
// std::vector<detail::transfer_t> transfer_funcs;
std::vector<operator_info> operators;
std::vector<detail::print_func_t> print_funcs;
std::vector<std::optional<std::string_view>> names;
@ -107,8 +95,8 @@ namespace blt::gp
explicit operator_builder(type_provider& system): system(system)
{}
template<typename Return, typename... Args>
operator_builder& add_operator(const operation_t<Return(Args...)>& op, bool is_static = false)
template<typename ArgType, typename Return, typename... Args>
operator_builder& add_operator(const operation_t<ArgType, Return(Args...)>& op, bool is_static = false)
{
auto return_type_id = system.get_type<Return>().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<Context>();
info.transfer = [](stack_allocator& to, stack_allocator& from) {
info.transfer = [](std::optional<std::reference_wrapper<stack_allocator>> to, stack_allocator& from) {
#if BLT_DEBUG_LEVEL >= 3
auto value = from.pop<Return>();
//BLT_TRACE_STREAM << value << "\n";
to.push(value);
if (to){
to->get().push(value);
}
#else
to.push(from.pop<Return>());
if (to)
{
to->get().push(from.pop<Return>());
} else
{
from.pop<Return>();
}
#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<mutation_t> mutator;
std::reference_wrapper<crossover_t> crossover;
std::reference_wrapper<population_initializer_t> 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<population_initializer_t>& 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<population_initializer_t>& popInitializer);
config_t(size_t populationSize, size_t initialMinTreeSize, size_t initialMaxTreeSize,
const std::reference_wrapper<mutation_t>& mutator, const std::reference_wrapper<crossover_t>& crossover,
const std::reference_wrapper<population_initializer_t>& 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<typename Return, typename Class, typename Container, typename Lambda = Return(Class::*)(tree_t, Container, blt::size_t) const>
void evaluate_fitness(Lambda&& fitness_function, Container& result_storage)
{
for (const auto& ind : blt::enumerate(current_pop.getIndividuals()))
ind.second.raw_fitness = static_cast<double>(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<double>(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;
};
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef BLT_GP_SELECTION_H
#define BLT_GP_SELECTION_H
#include <blt/gp/fwdecl.h>
#include <blt/gp/tree.h>
#include <blt/std/assert.h>
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

View File

@ -22,6 +22,7 @@
#include <blt/std/utility.h>
#include <blt/gp/fwdecl.h>
#include <blt/gp/tree.h>
#include <blt/gp/generators.h>
#include <blt/std/expected.h>
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<result_t, error_t> 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;
};
}

View File

@ -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<tree_t>& getIndividuals()
class population_tree_iterator
{
public:
population_tree_iterator(std::vector<individual>& 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<individual>& ind;
blt::size_t pos;
};
std::vector<individual>& getIndividuals()
{
return individuals;
}
population_tree_iterator for_each_tree()
{
return population_tree_iterator{individuals, 0};
}
private:
std::vector<tree_t> individuals;
std::vector<individual> individuals;
};
}

View File

@ -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);

View File

@ -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<mutation_t>& mutator, const std::reference_wrapper<crossover_t>& crossover,
const std::reference_wrapper<population_initializer_t>& 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<population_initializer_t>& 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<population_initializer_t>& popInitializer):
mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer)
{
}
}

88
src/selection.cpp Normal file
View File

@ -0,0 +1,88 @@
/*
* <Short Description>
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/gp/selection.h>
#include <blt/gp/program.h>
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)
{
}
}

View File

@ -24,7 +24,6 @@ namespace blt::gp
{
blt::expected<crossover_t::result_t, crossover_t::error_t> 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<blt::ptrdiff_t>(point);
auto end_p = ops.begin() + static_cast<blt::ptrdiff_t>(index);
stack_allocator after_stack;
//std::vector<op_container_t> 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<std::reference_wrapper<stack_allocator>>{}, 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<blt::ptrdiff_t>(new_end_point);
for (auto it = new_end_p; it != ops.end(); it++)
{
if (it->is_value)
it->transfer(vals, after_stack);
}
return c;
}
}