crossover changes

dev-0.2.1
Brett 2024-12-25 00:10:34 -05:00
parent 3e0fe06017
commit 5356be6649
6 changed files with 113 additions and 115 deletions

View File

@ -27,7 +27,7 @@ macro(compile_options target_name)
sanitizers(${target_name})
endmacro()
project(blt-gp VERSION 0.2.5)
project(blt-gp VERSION 0.2.6)
include(CTest)

View File

@ -30,11 +30,12 @@ namespace blt::gp
{
struct prog_config_t
{
blt::size_t population_size = 500;
blt::size_t max_generations = 50;
blt::size_t initial_min_tree_size = 3;
blt::size_t initial_max_tree_size = 10;
size_t population_size = 500;
size_t max_generations = 50;
size_t initial_min_tree_size = 2;
size_t initial_max_tree_size = 6;
size_t max_tree_depth = 17;
// percent chance that we will do crossover
double crossover_chance = 0.8;
// percent chance that we will do mutation
@ -42,96 +43,96 @@ namespace blt::gp
// percent chance we will do reproduction (copy individual)
double reproduction_chance = 0.1;
// everything else will just be selected
blt::size_t elites = 0;
size_t elites = 0;
bool try_mutation_on_crossover_failure = true;
std::reference_wrapper<mutation_t> mutator;
std::reference_wrapper<crossover_t> crossover;
std::reference_wrapper<population_initializer_t> pop_initializer;
blt::size_t threads = std::thread::hardware_concurrency();
// number of elements each thread should pull per execution. this is for granularity performance and can be optimized for better results!
blt::size_t evaluation_size = 4;
// default config (ramped half-and-half init) or for buildering
prog_config_t();
// default config with a user specified initializer
prog_config_t(const std::reference_wrapper<population_initializer_t>& popInitializer); // NOLINT
prog_config_t(size_t populationSize, const std::reference_wrapper<population_initializer_t>& popInitializer);
prog_config_t(size_t populationSize); // NOLINT
prog_config_t& set_pop_size(blt::size_t pop)
{
population_size = pop;
//evaluation_size = (population_size / threads) / 2;
return *this;
}
prog_config_t& set_initial_min_tree_size(blt::size_t size)
{
initial_min_tree_size = size;
return *this;
}
prog_config_t& set_initial_max_tree_size(blt::size_t size)
{
initial_max_tree_size = size;
return *this;
}
prog_config_t& set_crossover(crossover_t& ref)
{
crossover = {ref};
return *this;
}
prog_config_t& set_mutation(mutation_t& ref)
{
mutator = {ref};
return *this;
}
prog_config_t& set_initializer(population_initializer_t& ref)
{
pop_initializer = ref;
return *this;
}
prog_config_t& set_elite_count(blt::size_t new_elites)
{
elites = new_elites;
return *this;
}
prog_config_t& set_crossover_chance(double new_crossover_chance)
{
crossover_chance = new_crossover_chance;
return *this;
}
prog_config_t& set_mutation_chance(double new_mutation_chance)
{
mutation_chance = new_mutation_chance;
return *this;
}
prog_config_t& set_max_generations(blt::size_t new_max_generations)
{
max_generations = new_max_generations;
return *this;
}
prog_config_t& set_try_mutation_on_crossover_failure(bool new_try_mutation_on_crossover_failure)
{
try_mutation_on_crossover_failure = new_try_mutation_on_crossover_failure;
return *this;
}
prog_config_t& set_thread_count(blt::size_t t)
{
if (t == 0)
@ -140,13 +141,19 @@ namespace blt::gp
//evaluation_size = (population_size / threads) / 2;
return *this;
}
prog_config_t& set_evaluation_size(blt::size_t s)
{
evaluation_size = s;
return *this;
}
prog_config_t& set_max_tree_depth(const size_t depth)
{
max_tree_depth = depth;
return *this;
}
prog_config_t& set_reproduction_chance(double chance)
{
reproduction_chance = chance;

View File

@ -60,32 +60,30 @@ namespace blt::gp
public:
struct point_info_t
{
blt::ptrdiff_t point;
ptrdiff_t point;
operator_info_t& type_operator_info;
};
struct crossover_point_t
{
blt::ptrdiff_t p1_crossover_point;
blt::ptrdiff_t p2_crossover_point;
ptrdiff_t p1_crossover_point;
ptrdiff_t p2_crossover_point;
};
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
u32 max_crossover_tries = 5;
// if tree have fewer nodes than this number, they will not be considered for crossover
u32 min_tree_size = 3;
// should be at least 5 as crossover will not select the root node.
u32 min_tree_size = 5;
// used by the traverse version of get_crossover_point
// at each depth level, what chance do we have to exit with this as our point? or in other words what's the chance we continue traversing
// this is what this option configures.
f32 traverse_chance = 0.5;
// legacy settings:
// 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;
// how often should we select terminals over functions. By default, we only allow selection of terminals 10% of the time
// this applies to both types of crossover point functions. Traversal will use the parent if it should not pick a terminal.
f32 terminal_chance = 0.1;
// use traversal to select point instead of random selection
bool traverse = false;
};
crossover_t() = default;
@ -97,9 +95,7 @@ namespace blt::gp
std::optional<crossover_point_t> get_crossover_point_traverse(gp_program& program, const tree_t& c1, const tree_t& c2) const;
std::optional<point_info_t> get_point_traverse(gp_program& program, const tree_t& t, std::optional<type_id> type) const;
static std::optional<point_info_t> random_place_of_type(gp_program& program, const tree_t& t, type_id type);
std::optional<point_info_t> get_point_from_traverse_raw(gp_program& program, const tree_t& t, std::optional<type_id> type) const;
/**
* child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover.
@ -143,7 +139,7 @@ namespace blt::gp
virtual bool apply(gp_program& program, const tree_t& p, tree_t& c);
// returns the point after the mutation
blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node);
blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node) const;
virtual ~mutation_t() = default;

View File

@ -106,7 +106,7 @@ namespace blt::gp
return operations;
}
[[nodiscard]] inline blt::gp::stack_allocator& get_values()
[[nodiscard]] inline stack_allocator& get_values()
{
return values;
}
@ -127,7 +127,7 @@ namespace blt::gp
return (*func)(*this, nullptr);
}
blt::size_t get_depth(gp_program& program);
blt::size_t get_depth(gp_program& program) const;
/**
* Helper template for returning the result of the last evaluation

View File

@ -67,7 +67,12 @@ namespace blt::gp
auto& c1_ops = c1.get_operations();
auto& c2_ops = c2.get_operations();
const auto point = get_crossover_point(program, p1, p2);
std::optional<crossover_point_t> point;
if (config.traverse)
point = get_crossover_point_traverse(program, p1, p2);
else
point = get_crossover_point(program, p1, p2);
if (!point)
return false;
@ -88,28 +93,28 @@ namespace blt::gp
case 1:
{
// basic crossover
auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point);
const auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
const auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point);
auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point;
auto found_point_end_itr = c2_ops.begin() + c2.find_endpoint(program, point->p2_crossover_point);
const auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point;
const auto found_point_end_itr = c2_ops.begin() + c2.find_endpoint(program, point->p2_crossover_point);
stack_allocator& c1_stack = c1.get_values();
stack_allocator& c2_stack = c2.get_values();
for (const auto& op : blt::iterate(crossover_point_begin_itr, crossover_point_end_itr))
for (const auto& op : iterate(crossover_point_begin_itr, crossover_point_end_itr))
c1_operators.push_back(op);
for (const auto& op : blt::iterate(found_point_begin_itr, found_point_end_itr))
for (const auto& op : iterate(found_point_begin_itr, found_point_end_itr))
c2_operators.push_back(op);
blt::size_t c1_stack_after_bytes = accumulate_type_sizes(crossover_point_end_itr, c1_ops.end());
blt::size_t c1_stack_for_bytes = accumulate_type_sizes(crossover_point_begin_itr, crossover_point_end_itr);
blt::size_t c2_stack_after_bytes = accumulate_type_sizes(found_point_end_itr, c2_ops.end());
blt::size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr);
auto c1_total = static_cast<blt::ptrdiff_t>(c1_stack_after_bytes + c1_stack_for_bytes);
auto c2_total = static_cast<blt::ptrdiff_t>(c2_stack_after_bytes + c2_stack_for_bytes);
auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
const size_t c1_stack_after_bytes = accumulate_type_sizes(crossover_point_end_itr, c1_ops.end());
const size_t c1_stack_for_bytes = accumulate_type_sizes(crossover_point_begin_itr, crossover_point_end_itr);
const size_t c2_stack_after_bytes = accumulate_type_sizes(found_point_end_itr, c2_ops.end());
const size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr);
const auto c1_total = static_cast<blt::ptrdiff_t>(c1_stack_after_bytes + c1_stack_for_bytes);
const auto c2_total = static_cast<blt::ptrdiff_t>(c2_stack_after_bytes + c2_stack_for_bytes);
const auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
const auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
c1_stack.reserve(c1_stack.bytes_in_head() - c1_stack_for_bytes + c2_stack_for_bytes);
c2_stack.reserve(c2_stack.bytes_in_head() - c2_stack_for_bytes + c1_stack_for_bytes);
@ -170,6 +175,8 @@ namespace blt::gp
}
#endif
c1.get_depth(program);
return true;
}
@ -181,52 +188,32 @@ namespace blt::gp
size_t crossover_point = program.get_random().get_size_t(1ul, c1_ops.size());
while (config.avoid_terminals && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
const bool allow_terminal_selection = program.get_random().choice(config.terminal_chance);
blt::size_t counter = 0;
while (!allow_terminal_selection && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
{
if (counter >= config.max_crossover_tries)
return {};
crossover_point = program.get_random().get_size_t(1ul, c1_ops.size());
counter++;
}
size_t attempted_point = 0;
const auto& crossover_point_type = program.get_operator_info(c1_ops[crossover_point].id);
operator_info_t* attempted_point_type = nullptr;
const operator_info_t* attempted_point_type = nullptr;
blt::size_t counter = 0;
do
for (counter = 0; counter < config.max_crossover_tries; counter++)
{
if (counter >= config.max_crossover_tries)
{
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 {};
}
// should we try again over the whole tree? probably not.
return {};
}
attempted_point = program.get_random().get_size_t(1ul, c2_ops.size());
attempted_point_type = &program.get_operator_info(c2_ops[attempted_point].id);
if (config.avoid_terminals && attempted_point_type->argc.is_terminal())
if (!allow_terminal_selection && attempted_point_type->argc.is_terminal())
continue;
if (crossover_point_type.return_type == attempted_point_type->return_type)
break;
counter++;
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
}
while (true);
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
return {};
}
std::optional<crossover_t::crossover_point_t> crossover_t::get_crossover_point_traverse(gp_program& program, const tree_t& c1,
@ -241,25 +228,29 @@ namespace blt::gp
return {{c1_point_o->point, c2_point_o->point}};
}
std::optional<crossover_t::point_info_t> crossover_t::random_place_of_type(gp_program& program, const tree_t& t, type_id type)
{
auto attempted_point = program.get_random().get_i64(1ul, t.get_operations().size());
auto& attempted_point_type = program.get_operator_info(t.get_operations()[attempted_point].id);
if (type == attempted_point_type.return_type)
return {{attempted_point, attempted_point_type}};
return {};
}
std::optional<crossover_t::point_info_t> crossover_t::get_point_traverse(gp_program& program, const tree_t& t, std::optional<type_id> type) const
std::optional<crossover_t::point_info_t> crossover_t::get_point_from_traverse_raw(gp_program& program, const tree_t& t,
std::optional<type_id> type) const
{
auto& random = program.get_random();
bool should_select_terminal = program.get_random().choice(config.terminal_chance);
ptrdiff_t parent = -1;
ptrdiff_t point = 0;
while (true)
{
auto& current_op_type = program.get_operator_info(t.get_operations()[point].id);
if (current_op_type.argc.is_terminal())
{
if (!should_select_terminal)
{
if (parent == -1)
return {};
auto& parent_type = program.get_operator_info(t.get_operations()[parent].id);
if (type && *type != parent_type.return_type)
return {};
return {{parent, parent_type}};
}
if (type && *type != current_op_type.return_type)
return {};
return {{point, current_op_type}};
@ -270,6 +261,7 @@ namespace blt::gp
const auto args = current_op_type.argc.argc;
const auto argument = random.get_size_t(0, args);
parent = point;
// move to the first child
point += 1;
// loop through all the children we wish to skip. The result will be the first node of the next child, becoming the new parent
@ -288,7 +280,7 @@ namespace blt::gp
{
for (size_t i = 0; i < config.max_crossover_tries; i++)
{
if (auto found = get_point_traverse(program, t, type))
if (auto found = get_point_from_traverse_raw(program, t, type))
return found;
}
return {};
@ -300,7 +292,7 @@ namespace blt::gp
return true;
}
blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node)
blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node) const
{
auto& ops_r = c.get_operations();
auto& vals_r = c.get_values();

View File

@ -140,14 +140,17 @@ namespace blt::gp
out << '\n';
}
blt::size_t tree_t::get_depth(gp_program& program)
size_t tree_t::get_depth(gp_program& program) const
{
blt::size_t depth = 0;
size_t depth = 0;
auto operations_stack = operations;
tracked_vector<blt::size_t> values_process;
tracked_vector<blt::size_t> value_stack;
thread_local tracked_vector<size_t> values_process;
thread_local tracked_vector<size_t> value_stack;
values_process.clear();
value_stack.clear();
for (const auto& op : operations_stack)
{
if (op.is_value)
@ -167,8 +170,8 @@ namespace blt::gp
value_stack.pop_back();
continue;
}
blt::size_t local_depth = 0;
for (blt::size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
size_t local_depth = 0;
for (size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
{
local_depth = std::max(local_depth, values_process.back());
values_process.pop_back();