crossover working, test 5 now fully tests crossover on a production sized scale
parent
30a4a0e8d7
commit
f37582ab4a
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.25)
|
||||
project(blt-gp VERSION 0.0.49)
|
||||
project(blt-gp VERSION 0.0.50)
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
|
|
@ -37,32 +37,35 @@
|
|||
#include <blt/gp/tree.h>
|
||||
#include <blt/std/logging.h>
|
||||
#include <blt/gp/transformers.h>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
|
||||
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; }); // 0
|
||||
blt::gp::operation_t sub([](float a, float b) { return a - b; }); // 1
|
||||
blt::gp::operation_t mul([](float a, float b) { return a * b; }); // 2
|
||||
blt::gp::operation_t pro_div([](float a, float b) { return b == 0 ? 0.0f : a / b; }); // 3
|
||||
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; }); // 4
|
||||
blt::gp::operation_t eq_f([](float a, float b) { return a == b; }); // 5
|
||||
blt::gp::operation_t eq_b([](bool a, bool b) { return a == b; }); // 6
|
||||
blt::gp::operation_t lt([](float a, float b) { return a < b; }); // 7
|
||||
blt::gp::operation_t gt([](float a, float b) { return a > b; }); // 8
|
||||
blt::gp::operation_t op_and([](bool a, bool b) { return a && b; }); // 9
|
||||
blt::gp::operation_t op_or([](bool a, bool b) { return a || b; }); // 10
|
||||
blt::gp::operation_t op_xor([](bool a, bool b) { return static_cast<bool>(a ^ b); }); // 11
|
||||
blt::gp::operation_t op_not([](bool b) { return !b; }); // 12
|
||||
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
|
||||
|
@ -106,29 +109,88 @@ int main()
|
|||
blt::gp::crossover_t crossover;
|
||||
|
||||
auto& ind = pop.getIndividuals();
|
||||
auto results = crossover.apply(program, ind[0], ind[1]);
|
||||
BLT_INFO("Post crossover:");
|
||||
|
||||
if (results.has_value())
|
||||
std::vector<float> pre;
|
||||
std::vector<float> pos;
|
||||
blt::size_t errors = 0;
|
||||
BLT_INFO("Pre-Crossover:");
|
||||
for (auto& tree : pop.getIndividuals())
|
||||
{
|
||||
BLT_TRACE("Parent 1: %f", ind[0].get_evaluation_value<float>(nullptr));
|
||||
BLT_TRACE("Parent 2: %f", ind[1].get_evaluation_value<float>(nullptr));
|
||||
BLT_TRACE("------------");
|
||||
BLT_TRACE("Child 1: %f", results->child1.get_evaluation_value<float>(nullptr));
|
||||
BLT_TRACE("Child 2: %f", results->child2.get_evaluation_value<float>(nullptr));
|
||||
} else
|
||||
auto f = tree.get_evaluation_value<float>(nullptr);
|
||||
pre.push_back(f);
|
||||
BLT_TRACE(f);
|
||||
}
|
||||
|
||||
BLT_INFO("Crossover:");
|
||||
blt::gp::population_t new_pop;
|
||||
while (new_pop.getIndividuals().size() < pop.getIndividuals().size())
|
||||
{
|
||||
switch (results.error())
|
||||
auto& random = program.get_random();
|
||||
std::uniform_int_distribution dist(0ul, pop.getIndividuals().size() - 1);
|
||||
blt::size_t first = dist(random);
|
||||
blt::size_t second;
|
||||
do
|
||||
{
|
||||
case blt::gp::crossover_t::error_t::NO_VALID_TYPE:
|
||||
BLT_ERROR("No valid type!");
|
||||
break;
|
||||
case blt::gp::crossover_t::error_t::TREE_TOO_SMALL:
|
||||
BLT_ERROR("Tree is too small!");
|
||||
break;
|
||||
second = dist(random);
|
||||
} while (second == first);
|
||||
|
||||
auto results = crossover.apply(program, ind[first], ind[second]);
|
||||
if (results.has_value())
|
||||
{
|
||||
// bool print_literals = true;
|
||||
// bool pretty_print = false;
|
||||
// bool print_returns = false;
|
||||
// BLT_TRACE("Parent 1: %f", ind[0].get_evaluation_value<float>(nullptr));
|
||||
// ind[0].print(program, std::cout, print_literals, pretty_print, print_returns);
|
||||
// BLT_TRACE("Parent 2: %f", ind[1].get_evaluation_value<float>(nullptr));
|
||||
// ind[1].print(program, std::cout, print_literals, pretty_print, print_returns);
|
||||
// BLT_TRACE("------------");
|
||||
// BLT_TRACE("Child 1: %f", results->child1.get_evaluation_value<float>(nullptr));
|
||||
// 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));
|
||||
} else
|
||||
{
|
||||
switch (results.error())
|
||||
{
|
||||
case blt::gp::crossover_t::error_t::NO_VALID_TYPE:
|
||||
BLT_ERROR("No valid type!");
|
||||
break;
|
||||
case blt::gp::crossover_t::error_t::TREE_TOO_SMALL:
|
||||
BLT_ERROR("Tree is too small!");
|
||||
break;
|
||||
}
|
||||
errors++;
|
||||
new_pop.getIndividuals().push_back(ind[first]);
|
||||
new_pop.getIndividuals().push_back(ind[second]);
|
||||
}
|
||||
}
|
||||
|
||||
BLT_INFO("Post-Crossover:");
|
||||
for (auto& tree : new_pop.getIndividuals())
|
||||
{
|
||||
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);
|
||||
BLT_INFO("Error times: %ld", errors);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <functional>
|
||||
#include <blt/std/logging.h>
|
||||
#include <blt/std/types.h>
|
||||
#include <ostream>
|
||||
|
||||
namespace blt::gp
|
||||
{
|
||||
|
@ -55,6 +56,8 @@ namespace blt::gp
|
|||
using callable_t = std::function<void(void*, stack_allocator&, stack_allocator&)>;
|
||||
// to, from
|
||||
using transfer_t = std::function<void(stack_allocator&, stack_allocator&)>;
|
||||
// debug function,
|
||||
using print_func_t = std::function<void(std::ostream&, stack_allocator&)>;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <blt/gp/stack.h>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
namespace blt::gp
|
||||
{
|
||||
|
@ -83,7 +84,7 @@ namespace blt::gp
|
|||
|
||||
template<typename Func, blt::u64... indices, typename... ExtraArgs>
|
||||
inline static constexpr Return exec_sequence_to_indices(Func&& func, stack_allocator& allocator, std::integer_sequence<blt::u64, indices...>,
|
||||
ExtraArgs&&... args)
|
||||
ExtraArgs&& ... args)
|
||||
{
|
||||
// expands Args and indices, providing each argument with its index calculating the current argument byte offset
|
||||
return std::forward<Func>(func)(std::forward<ExtraArgs>(args)..., allocator.from<Args>(getByteOffset<indices>())...);
|
||||
|
@ -120,7 +121,7 @@ namespace blt::gp
|
|||
constexpr operation_t(operation_t&& move) = default;
|
||||
|
||||
template<typename Functor>
|
||||
constexpr explicit operation_t(const Functor& functor): func(functor)
|
||||
constexpr explicit operation_t(const Functor& functor, std::optional<std::string_view> name = {}): func(functor), name(name)
|
||||
{}
|
||||
|
||||
[[nodiscard]] constexpr inline Return operator()(stack_allocator& read_allocator) const
|
||||
|
@ -171,9 +172,15 @@ namespace blt::gp
|
|||
{
|
||||
return sizeof...(Args);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr std::optional<std::string_view> get_name() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
private:
|
||||
function_t func;
|
||||
std::optional<std::string_view> name;
|
||||
};
|
||||
|
||||
template<typename Return, typename Class, typename... Args>
|
||||
|
@ -188,6 +195,12 @@ namespace blt::gp
|
|||
|
||||
template<typename Return, typename... Args>
|
||||
operation_t(Return(*)(Args...)) -> operation_t<Return(Args...)>;
|
||||
|
||||
template<typename Lambda>
|
||||
operation_t(Lambda, std::optional<std::string_view>) -> operation_t<decltype(&Lambda::operator())>;
|
||||
|
||||
template<typename Return, typename... Args>
|
||||
operation_t(Return(*)(Args...), std::optional<std::string_view>) -> operation_t<Return(Args...)>;
|
||||
|
||||
// templat\e<typename Return, typename Class, typename... Args>
|
||||
// operation_t<Return(Args...)> make_operator(Return (Class::*)(Args...) const lambda)
|
||||
|
|
|
@ -48,6 +48,11 @@ namespace blt::gp
|
|||
{
|
||||
blt::u32 argc = 0;
|
||||
blt::u32 argc_context = 0;
|
||||
|
||||
[[nodiscard]] bool is_terminal() const
|
||||
{
|
||||
return argc == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct config_t
|
||||
|
@ -56,14 +61,21 @@ namespace blt::gp
|
|||
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
|
||||
std::vector<type_id> argument_types;
|
||||
// return type of this operator
|
||||
type_id return_type;
|
||||
// number of arguments for this operator
|
||||
argc_t argc;
|
||||
// function to call this operator
|
||||
detail::callable_t function;
|
||||
// function used to transfer values between stacks
|
||||
detail::transfer_t transfer;
|
||||
};
|
||||
|
||||
|
@ -80,6 +92,8 @@ namespace blt::gp
|
|||
// 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;
|
||||
};
|
||||
|
||||
template<typename Context = detail::empty_t>
|
||||
|
@ -120,7 +134,7 @@ namespace blt::gp
|
|||
info.transfer = [](stack_allocator& to, stack_allocator& from) {
|
||||
#if BLT_DEBUG_LEVEL >= 3
|
||||
auto value = from.pop<Return>();
|
||||
BLT_TRACE_STREAM << value << "\n";
|
||||
//BLT_TRACE_STREAM << value << "\n";
|
||||
to.push(value);
|
||||
#else
|
||||
to.push(from.pop<Return>());
|
||||
|
@ -128,6 +142,10 @@ namespace blt::gp
|
|||
|
||||
};
|
||||
storage.operators.push_back(info);
|
||||
storage.print_funcs.push_back([](std::ostream& out, stack_allocator& stack) {
|
||||
out << stack.pop<Return>();
|
||||
});
|
||||
storage.names.push_back(op.get_name());
|
||||
if (is_static)
|
||||
storage.static_types.insert(operator_id);
|
||||
return *this;
|
||||
|
@ -279,6 +297,16 @@ namespace blt::gp
|
|||
return storage.operators[id];
|
||||
}
|
||||
|
||||
inline detail::print_func_t& get_print_func(operator_id id)
|
||||
{
|
||||
return storage.print_funcs[id];
|
||||
}
|
||||
|
||||
inline std::optional<std::string_view> get_name(operator_id id)
|
||||
{
|
||||
return storage.names[id];
|
||||
}
|
||||
|
||||
inline std::vector<operator_id>& get_type_terminals(type_id id)
|
||||
{
|
||||
return storage.terminals[id];
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <utility>
|
||||
#include <stack>
|
||||
#include <ostream>
|
||||
|
||||
namespace blt::gp
|
||||
{
|
||||
|
@ -100,6 +101,8 @@ namespace blt::gp
|
|||
auto results = evaluate(context);
|
||||
return results.values.pop<T>();
|
||||
}
|
||||
|
||||
void print(gp_program& program, std::ostream& output, bool print_literals = true, bool pretty_indent = false, bool include_types = false);
|
||||
|
||||
private:
|
||||
std::vector<op_container_t> operations;
|
||||
|
|
|
@ -45,6 +45,10 @@ namespace blt::gp
|
|||
std::uniform_int_distribution op_sel2(3ul, c2_ops.size() - 1);
|
||||
|
||||
blt::size_t crossover_point = op_sel1(program.get_random());
|
||||
|
||||
while (config.avoid_terminals && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
|
||||
crossover_point = op_sel1(program.get_random());
|
||||
|
||||
blt::size_t attempted_point = 0;
|
||||
|
||||
const auto& crossover_point_type = program.get_operator_info(c1_ops[crossover_point].id);
|
||||
|
@ -57,16 +61,22 @@ namespace blt::gp
|
|||
{
|
||||
if (config.should_crossover_try_forward)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto i = attempted_point + 1; i < c2_ops.size(); i++)
|
||||
{
|
||||
auto* info = &program.get_operator_info(c2_ops[i].id);
|
||||
if (info->return_type == crossover_point_type.return_type)
|
||||
{
|
||||
if (config.avoid_terminals && info->argc.is_terminal())
|
||||
continue;
|
||||
attempted_point = i;
|
||||
attempted_point_type = info;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return blt::unexpected(error_t::NO_VALID_TYPE);
|
||||
}
|
||||
// should we try again over the whole tree? probably not.
|
||||
return blt::unexpected(error_t::NO_VALID_TYPE);
|
||||
|
@ -74,9 +84,13 @@ namespace blt::gp
|
|||
{
|
||||
attempted_point = op_sel2(program.get_random());
|
||||
attempted_point_type = &program.get_operator_info(c2_ops[attempted_point].id);
|
||||
if (config.avoid_terminals && attempted_point_type->argc.is_terminal())
|
||||
continue;
|
||||
if (crossover_point_type.return_type == attempted_point_type->return_type)
|
||||
break;
|
||||
counter++;
|
||||
}
|
||||
} while (crossover_point_type.return_type != attempted_point_type->return_type);
|
||||
} while (true);
|
||||
|
||||
blt::i64 children_left = 0;
|
||||
blt::size_t index = crossover_point;
|
||||
|
@ -85,12 +99,15 @@ namespace blt::gp
|
|||
{
|
||||
const auto& type = program.get_operator_info(c1_ops[index].id);
|
||||
#if BLT_DEBUG_LEVEL > 1
|
||||
BLT_TRACE("Crossover type: %s, op %ld", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), c1_ops[index].id);
|
||||
#define MAKE_C_STR() program.get_name(c1_ops[index].id).has_value() ? std::string(program.get_name(c1_ops[index].id).value()).c_str() : std::to_string(c1_ops[index].id).c_str()
|
||||
BLT_TRACE("Crossover type: %s, op: %s", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), MAKE_C_STR());
|
||||
#undef MAKE_C_STR
|
||||
#endif
|
||||
// this is a child to someone
|
||||
if (children_left != 0)
|
||||
children_left--;
|
||||
if (type.argc.argc > 0)
|
||||
children_left += type.argc.argc;
|
||||
else
|
||||
children_left--;
|
||||
index++;
|
||||
} while (children_left > 0);
|
||||
|
||||
|
@ -107,12 +124,16 @@ namespace blt::gp
|
|||
{
|
||||
const auto& type = program.get_operator_info(c2_ops[index].id);
|
||||
#if BLT_DEBUG_LEVEL > 1
|
||||
BLT_TRACE("Found type: %s, op: %ld", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), c2_ops[index].id);
|
||||
#define MAKE_C_STR() program.get_name(c2_ops[index].id).has_value() ? std::string(program.get_name(c2_ops[index].id).value()).c_str() : std::to_string(c2_ops[index].id).c_str()
|
||||
BLT_TRACE("Found type: %s, op: %s", std::string(program.get_typesystem().get_type(type.return_type).name()).c_str(), MAKE_C_STR());
|
||||
#undef MAKE_C_STR
|
||||
#endif
|
||||
// this is a child to someone
|
||||
if (children_left != 0)
|
||||
children_left--;
|
||||
if (type.argc.argc > 0)
|
||||
children_left += type.argc.argc;
|
||||
else
|
||||
children_left--;
|
||||
|
||||
index++;
|
||||
} while (children_left > 0);
|
||||
|
||||
|
|
100
src/tree.cpp
100
src/tree.cpp
|
@ -19,6 +19,8 @@
|
|||
#include <blt/gp/stack.h>
|
||||
#include <blt/std/assert.h>
|
||||
#include <blt/std/logging.h>
|
||||
#include <blt/gp/program.h>
|
||||
#include <stack>
|
||||
|
||||
namespace blt::gp
|
||||
{
|
||||
|
@ -49,4 +51,102 @@ namespace blt::gp
|
|||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::ostream& create_indent(std::ostream& out, blt::size_t amount, bool pretty_print)
|
||||
{
|
||||
if (!pretty_print)
|
||||
return out;
|
||||
for (blt::size_t i = 0; i < amount; i++)
|
||||
out << '\t';
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string_view end_indent(bool pretty_print)
|
||||
{
|
||||
return pretty_print ? "\n" : "";
|
||||
}
|
||||
|
||||
std::string get_return_type(gp_program& program, type_id id, bool use_returns)
|
||||
{
|
||||
if (!use_returns)
|
||||
return "";
|
||||
return "(" + std::string(program.get_typesystem().get_type(id).name()) + ")";
|
||||
}
|
||||
|
||||
void tree_t::print(gp_program& program, std::ostream& out, bool print_literals, bool pretty_print, bool include_types)
|
||||
{
|
||||
std::stack<blt::size_t> arguments_left;
|
||||
blt::size_t indent = 0;
|
||||
|
||||
stack_allocator reversed;
|
||||
if (print_literals)
|
||||
{
|
||||
// I hate this.
|
||||
stack_allocator copy = values;
|
||||
|
||||
// reverse the order of the stack
|
||||
for (const auto& v : operations)
|
||||
{
|
||||
if (v.is_value)
|
||||
v.transfer(reversed, copy);
|
||||
}
|
||||
}
|
||||
for (const auto& v : operations)
|
||||
{
|
||||
auto info = program.get_operator_info(v.id);
|
||||
auto name = program.get_name(v.id) ? program.get_name(v.id).value() : "NULL";
|
||||
auto return_type = get_return_type(program, info.return_type, include_types);
|
||||
if (info.argc.argc > 0)
|
||||
{
|
||||
create_indent(out, indent, pretty_print) << "(";
|
||||
indent++;
|
||||
arguments_left.emplace(info.argc.argc);
|
||||
out << name << return_type << end_indent(pretty_print);
|
||||
} else
|
||||
{
|
||||
if (print_literals)
|
||||
{
|
||||
create_indent(out, indent, pretty_print);
|
||||
program.get_print_func(v.id)(out, reversed);
|
||||
out << return_type << end_indent(pretty_print);
|
||||
} else
|
||||
create_indent(out, indent, pretty_print) << name << return_type << end_indent(pretty_print);
|
||||
}
|
||||
|
||||
while (!arguments_left.empty())
|
||||
{
|
||||
auto top = arguments_left.top();
|
||||
arguments_left.pop();
|
||||
if (top == 0)
|
||||
{
|
||||
indent--;
|
||||
create_indent(out, indent, pretty_print) << ")" << end_indent(pretty_print);
|
||||
continue;
|
||||
} else
|
||||
{
|
||||
if (!pretty_print)
|
||||
out << " ";
|
||||
arguments_left.push(top - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!arguments_left.empty())
|
||||
{
|
||||
auto top = arguments_left.top();
|
||||
arguments_left.pop();
|
||||
if (top == 0)
|
||||
{
|
||||
indent--;
|
||||
create_indent(out, indent, pretty_print) << ")" << end_indent(pretty_print);
|
||||
continue;
|
||||
} else
|
||||
{
|
||||
BLT_ERROR("Failed to print tree correctly!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out << '\n';
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue