cleanup crossover function

thread
Brett 2024-07-20 13:02:41 -04:00
parent 5d72923998
commit fc7cd292af
3 changed files with 97 additions and 150 deletions

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
project(blt-gp VERSION 0.0.90) project(blt-gp VERSION 0.0.91)
include(CTest) include(CTest)

View File

@ -31,6 +31,7 @@ namespace blt::gp
class crossover_t class crossover_t
{ {
public: public:
using op_iter = std::vector<blt::gp::op_container_t>::iterator;
enum class error_t enum class error_t
{ {
NO_VALID_TYPE, NO_VALID_TYPE,
@ -41,6 +42,11 @@ namespace blt::gp
tree_t child1; tree_t child1;
tree_t child2; tree_t child2;
}; };
struct crossover_point_t
{
blt::ptrdiff_t p1_crossover_point;
blt::ptrdiff_t p2_crossover_point;
};
struct config_t 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 // 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
@ -56,6 +62,14 @@ namespace blt::gp
explicit crossover_t(const config_t& config): config(config) explicit crossover_t(const config_t& config): config(config)
{} {}
blt::expected<crossover_t::crossover_point_t, error_t> get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const;
static blt::ptrdiff_t find_endpoint(blt::gp::gp_program& program, const std::vector<blt::gp::op_container_t>& container,
blt::ptrdiff_t start);
static void transfer_forward(blt::gp::stack_allocator& from, blt::gp::stack_allocator& to, op_iter begin, op_iter end);
static void transfer_backward(blt::gp::stack_allocator& from, blt::gp::stack_allocator& to, op_iter begin, op_iter end);
/** /**
* child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover. * child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover.
* the parents are not modified during this process * the parents are not modified during this process
@ -68,7 +82,7 @@ namespace blt::gp
virtual ~crossover_t() = default; virtual ~crossover_t() = default;
private: protected:
config_t config; config_t config;
}; };
@ -97,7 +111,7 @@ namespace blt::gp
virtual ~mutation_t() = default; virtual ~mutation_t() = default;
private: protected:
config_t config; config_t config;
}; };

View File

@ -27,11 +27,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 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
{ {
result_t result{p1, p2}; result_t result{p1, p2};
#if BLT_DEBUG_LEVEL > 0
BLT_INFO("Child 1 Stack empty? %s", result.child1.get_values().empty() ? "true" : "false");
BLT_INFO("Child 2 Stack empty? %s", result.child2.get_values().empty() ? "true" : "false");
#endif
auto& c1 = result.child1; auto& c1 = result.child1;
auto& c2 = result.child2; auto& c2 = result.child2;
@ -42,6 +37,67 @@ namespace blt::gp
if (c1_ops.size() < 5 || c2_ops.size() < 5) if (c1_ops.size() < 5 || c2_ops.size() < 5)
return blt::unexpected(error_t::TREE_TOO_SMALL); return blt::unexpected(error_t::TREE_TOO_SMALL);
auto point = get_crossover_point(program, c1, c2);
if (!point)
return blt::unexpected(point.error());
auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
auto crossover_point_end_itr = c1_ops.begin() + find_endpoint(program, c1_ops, point->p1_crossover_point);
auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point;
auto found_point_end_itr = c2_ops.begin() + find_endpoint(program, c2_ops, point->p2_crossover_point);
stack_allocator& c1_stack_init = c1.get_values();
stack_allocator& c2_stack_init = c2.get_values();
std::vector<op_container_t> c1_operators;
std::vector<op_container_t> c2_operators;
for (const auto& op : blt::enumerate(crossover_point_begin_itr, crossover_point_end_itr))
c1_operators.push_back(op);
for (const auto& op : blt::enumerate(found_point_begin_itr, found_point_end_itr))
c2_operators.push_back(op);
stack_allocator c1_stack_after_copy;
stack_allocator c1_stack_for_copy;
stack_allocator c2_stack_after_copy;
stack_allocator c2_stack_for_copy;
// transfer all values after the crossover point. these will need to be transferred back to child2
transfer_backward(c1_stack_init, c1_stack_after_copy, c1_ops.end()-1, crossover_point_end_itr - 1);
// transfer all values for the crossover point.
transfer_backward(c1_stack_init, c1_stack_for_copy, crossover_point_end_itr - 1, crossover_point_begin_itr - 1);
// transfer child2 values for copying back into c1
transfer_backward(c2_stack_init, c2_stack_after_copy, c2_ops.end() - 1, found_point_end_itr - 1);
transfer_backward(c2_stack_init, c2_stack_for_copy, found_point_end_itr - 1, found_point_begin_itr - 1);
// now copy back into the respective children
transfer_forward(c2_stack_for_copy, c1.get_values(), found_point_begin_itr, found_point_end_itr);
transfer_forward(c1_stack_for_copy, c2.get_values(), crossover_point_begin_itr, crossover_point_end_itr);
// now copy after the crossover point back to the correct children
transfer_forward(c1_stack_after_copy, c1.get_values(), crossover_point_end_itr, c1_ops.end());
transfer_forward(c2_stack_after_copy, c2.get_values(), found_point_end_itr, c2_ops.end());
// now swap the operators
auto insert_point_c1 = crossover_point_begin_itr - 1;
auto insert_point_c2 = found_point_begin_itr - 1;
// invalidates [begin, end()) so the insert points should be fine
c1_ops.erase(crossover_point_begin_itr, crossover_point_end_itr);
c2_ops.erase(found_point_begin_itr, found_point_end_itr);
c1_ops.insert(++insert_point_c1, c2_operators.begin(), c2_operators.end());
c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end());
return result;
}
blt::expected<crossover_t::crossover_point_t, crossover_t::error_t> crossover_t::get_crossover_point(gp_program& program, const tree_t& c1,
const tree_t& c2) const
{
auto& c1_ops = c1.get_operations();
auto& c2_ops = c2.get_operations();
blt::size_t crossover_point = program.get_random().get_size_t(1ul, c1_ops.size()); blt::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()) while (config.avoid_terminals && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
@ -90,17 +146,16 @@ namespace blt::gp
} }
} while (true); } while (true);
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
}
blt::ptrdiff_t crossover_t::find_endpoint(blt::gp::gp_program& program, const std::vector<blt::gp::op_container_t>& container, blt::ptrdiff_t index)
{
blt::i64 children_left = 0; blt::i64 children_left = 0;
blt::size_t index = crossover_point;
do do
{ {
const auto& type = program.get_operator_info(c1_ops[index].id); const auto& type = program.get_operator_info(container[index].id);
#if BLT_DEBUG_LEVEL > 1
#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 // this is a child to someone
if (children_left != 0) if (children_left != 0)
children_left--; children_left--;
@ -109,148 +164,26 @@ namespace blt::gp
index++; index++;
} while (children_left > 0); } while (children_left > 0);
auto crossover_point_begin_itr = c1_ops.begin() + static_cast<blt::ptrdiff_t>(crossover_point); return index;
auto crossover_point_end_itr = c1_ops.begin() + static_cast<blt::ptrdiff_t>(index); }
#if BLT_DEBUG_LEVEL > 0
BLT_TRACE("[%ld %ld) %ld", crossover_point, index, index - crossover_point); void crossover_t::transfer_backward(stack_allocator& from, stack_allocator& to, crossover_t::op_iter begin, crossover_t::op_iter end)
#endif {
for (auto it = begin; it != end; it--)
children_left = 0;
index = attempted_point;
do
{
const auto& type = program.get_operator_info(c2_ops[index].id);
#if BLT_DEBUG_LEVEL > 1
#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;
index++;
} while (children_left > 0);
auto found_point_begin_itr = c2_ops.begin() + static_cast<blt::ptrdiff_t>(attempted_point);
auto found_point_end_itr = c2_ops.begin() + static_cast<blt::ptrdiff_t>(index);
#if BLT_DEBUG_LEVEL > 0
BLT_TRACE("[%ld %ld) %ld", attempted_point, index, index - attempted_point);
#endif
stack_allocator& c1_stack_init = c1.get_values();
stack_allocator& c2_stack_init = c2.get_values();
std::vector<op_container_t> c1_operators;
std::vector<op_container_t> c2_operators;
for (const auto& op : blt::enumerate(crossover_point_begin_itr, crossover_point_end_itr))
c1_operators.push_back(op);
for (const auto& op : blt::enumerate(found_point_begin_itr, found_point_end_itr))
c2_operators.push_back(op);
#if BLT_DEBUG_LEVEL > 0
BLT_TRACE("Sizes: %ld %ld || Ops size: %ld %ld", c1_operators.size(), c2_operators.size(), c1_ops.size(), c2_ops.size());
#endif
stack_allocator c1_stack_after_copy;
stack_allocator c1_stack_for_copy;
stack_allocator c2_stack_after_copy;
stack_allocator c2_stack_for_copy;
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring past crossover 1:");
#endif
// transfer all values after the crossover point. these will need to be transferred back to child2
for (auto it = c1_ops.end() - 1; it != crossover_point_end_itr - 1; it--)
{ {
if (it->is_value) if (it->is_value)
c1_stack_init.transfer_bytes(c1_stack_after_copy, it->type_size); from.transfer_bytes(to, it->type_size);
} }
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring for crossover 1:"); void crossover_t::transfer_forward(stack_allocator& from, stack_allocator& to, crossover_t::op_iter begin, crossover_t::op_iter end)
#endif {
// transfer all values for the crossover point.
for (auto it = crossover_point_end_itr - 1; it != crossover_point_begin_itr - 1; it--)
{
if (it->is_value)
c1_stack_init.transfer_bytes(c1_stack_for_copy, it->type_size);
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring past crossover 2:");
#endif
// transfer child2 values for copying back into c1
for (auto it = c2_ops.end() - 1; it != found_point_end_itr - 1; it--)
{
if (it->is_value)
c2_stack_init.transfer_bytes(c2_stack_after_copy, it->type_size);
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring for crossover 2:");
#endif
for (auto it = found_point_end_itr - 1; it != found_point_begin_itr - 1; it--)
{
if (it->is_value)
c2_stack_init.transfer_bytes(c2_stack_for_copy, it->type_size);
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring back for crossover 1:");
#endif
// now copy back into the respective children // now copy back into the respective children
for (auto it = found_point_begin_itr; it != found_point_end_itr; it++) for (auto it = begin; it != end; it++)
{ {
if (it->is_value) if (it->is_value)
c2_stack_for_copy.transfer_bytes(c1.get_values(), it->type_size); from.transfer_bytes(to, it->type_size);
} }
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring back for crossover 2:");
#endif
for (auto it = crossover_point_begin_itr; it != crossover_point_end_itr; it++)
{
if (it->is_value)
c1_stack_for_copy.transfer_bytes(c2.get_values(), it->type_size);
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring back after crossover 1:");
#endif
// now copy after the crossover point back to the correct children
for (auto it = crossover_point_end_itr; it != c1_ops.end(); it++)
{
if (it->is_value)
c1_stack_after_copy.transfer_bytes(c1.get_values(), it->type_size);
}
#if BLT_DEBUG_LEVEL > 1
BLT_DEBUG("Transferring back after crossover 2:");
#endif
for (auto it = found_point_end_itr; it != c2_ops.end(); it++)
{
if (it->is_value)
c2_stack_after_copy.transfer_bytes(c2.get_values(), it->type_size);
}
// now swap the operators
auto insert_point_c1 = crossover_point_begin_itr - 1;
auto insert_point_c2 = found_point_begin_itr - 1;
// invalidates [begin, end()) so the insert points should be fine
c1_ops.erase(crossover_point_begin_itr, crossover_point_end_itr);
c2_ops.erase(found_point_begin_itr, found_point_end_itr);
c1_ops.insert(++insert_point_c1, c2_operators.begin(), c2_operators.end());
c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end());
return result;
} }
tree_t mutation_t::apply(gp_program& program, const tree_t& p) tree_t mutation_t::apply(gp_program& program, const tree_t& p)