Begin working on adding drop function, add tests for many symbolic regression configs + performance

dev-func-drop
Brett 2025-01-11 18:25:55 -05:00
parent a497a006ee
commit ca0f10b410
16 changed files with 468 additions and 246 deletions

View File

@ -27,7 +27,7 @@ macro(compile_options target_name)
sanitizers(${target_name})
endmacro()
project(blt-gp VERSION 0.2.10)
project(blt-gp VERSION 0.3.0)
include(CTest)
@ -87,9 +87,9 @@ macro(blt_add_project name source type)
target_compile_definitions(${name}-${type} PRIVATE BLT_TRACK_ALLOCATIONS=1)
endif ()
add_test(NAME ${name} COMMAND ${name}-${type})
add_test(NAME ${name}-${type} COMMAND ${name}-${type})
set_property(TEST ${name} PROPERTY FAIL_REGULAR_EXPRESSION "FAIL;ERROR;FATAL;exception")
set_property(TEST ${name}-${type} PROPERTY FAIL_REGULAR_EXPRESSION "FAIL;ERROR;FATAL;exception")
project(blt-gp)
endmacro()
@ -115,4 +115,6 @@ if (${BUILD_GP_TESTS})
blt_add_project(blt-gp6 tests/old/gp_test_6.cpp test)
blt_add_project(blt-gp7 tests/old/gp_test_7.cpp test)
blt_add_project(blt-symbolic-regression tests/symbolic_regression_test.cpp test)
endif ()

View File

@ -124,6 +124,7 @@ bool blt::gp::example::rice_classification_t::fitness_function(const tree_t& cur
{
for (auto& training_case : training_cases)
{
BLT_GP_UPDATE_CONTEXT(training_case);
const auto v = current_tree.get_evaluation_value<float>(training_case);
switch (training_case.type)
{

View File

@ -62,48 +62,3 @@ int main()
return 0;
}
bool blt::gp::example::symbolic_regression_t::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<float>(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<size_t>(fitness.hits) == training_cases.size();
}
void blt::gp::example::symbolic_regression_t::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();
static operation_t op_x([](const context& context)
{
return context.x;
}, "x");
operator_builder<context> builder{};
builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x);
program.set_operations(builder.grab());
}

View File

@ -35,7 +35,25 @@ namespace blt::gp::example
};
private:
bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const;
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<float>(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<size_t>(fitness.hits) == training_cases.size();
}
static float example_function(const float x)
{
@ -63,7 +81,31 @@ namespace blt::gp::example
};
}
void setup_operations();
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();
static operation_t op_x([](const context& context)
{
return context.x;
}, "x");
operator_builder<context> 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()
{

View File

@ -20,12 +20,29 @@
#define BLT_GP_ALLOCATOR_H
#include <blt/std/types.h>
#include <blt/std/logging.h>
#include <blt/gp/util/trackers.h>
#include <cstdlib>
#include <vector>
namespace blt::gp
{
namespace detail
{
static constexpr inline size_t MAX_ALIGNMENT = 8;
#if BLT_DEBUG_LEVEL > 0
static void check_alignment(const size_t bytes, const std::string& message = "Invalid alignment")
{
if (bytes % MAX_ALIGNMENT != 0)
{
BLT_ABORT((message + ", expected multiple of " + std::to_string(detail::MAX_ALIGNMENT) + " got "
+ std::to_string(bytes)).c_str());
}
}
#endif
}
#ifdef BLT_TRACK_ALLOCATIONS
inline allocation_tracker_t tracker;
@ -47,11 +64,14 @@ namespace blt::gp
public:
void* allocate(blt::size_t bytes) // NOLINT
{
#ifdef BLT_TRACK_ALLOCATIONS
#ifdef BLT_TRACK_ALLOCATIONSS
tracker.allocate(bytes);
// std::cout << "Hey our aligned allocator allocated " << bytes << " bytes!\n";
#endif
return std::aligned_alloc(8, bytes);
#if (BLT_DEBUG_LEVEL > 0)
detail::check_alignment(bytes);
#endif
return std::aligned_alloc(detail::MAX_ALIGNMENT, bytes);
}
void deallocate(void* ptr, blt::size_t bytes) // NOLINT
@ -62,7 +82,7 @@ namespace blt::gp
tracker.deallocate(bytes);
// std::cout << "[Hey our aligned allocator deallocated " << bytes << " bytes!]\n";
#else
(void) bytes;
(void)bytes;
#endif
std::free(ptr);
}
@ -108,7 +128,7 @@ namespace blt::gp
::blt::gp::tracker.deallocate(n * sizeof(T));
// std::cout << "[Hey our tracked allocator deallocated " << (n * sizeof(T)) << " bytes!]\n";
#else
(void) n;
(void)n;
#endif
std::free(p);
}
@ -147,7 +167,7 @@ namespace blt::gp
template<typename T>
using tracked_vector = std::vector<T, tracked_allocator_t<T>>;
#else
template<typename T>
template <typename T>
using tracked_vector = std::vector<T>;
#endif
}

View File

@ -85,6 +85,18 @@ namespace blt::gp
using const_op_iter_t = tracked_vector<op_container_t>::const_iterator;
using op_iter_t = tracked_vector<op_container_t>::iterator;
}
#if BLT_DEBUG_LEVEL > 0
namespace detail::debug
{
inline void* context_ptr;
}
#define BLT_GP_UPDATE_CONTEXT(context) blt::gp::detail::debug::context_ptr = const_cast<void*>(static_cast<const void*>(&context))
#else
#define BLT_GP_UPDATE_CONTEXT(context)
#endif
}

View File

@ -143,13 +143,13 @@ namespace blt::gp
for (const auto& operation : iterate(ops).rev())
{
op_pos++;
if (operation.is_value)
if (operation.is_value())
{
total_so_far += stack_allocator::aligned_size(operation.type_size);
results.values.copy_from(vals.from(total_so_far), stack_allocator::aligned_size(operation.type_size));
total_so_far += operation.type_size();
results.values.copy_from(vals.from(total_so_far), operation.type_size());
continue;
}
call_jmp_table(operation.id, context, results.values, results.values, operators...);
call_jmp_table(operation.id(), context, results.values, results.values, operators...);
}
return results;
@ -258,9 +258,9 @@ namespace blt::gp
operator_metadata_t meta;
if constexpr (sizeof...(Args) != 0)
{
meta.arg_size_bytes = (stack_allocator::aligned_size(sizeof(Args)) + ...);
meta.arg_size_bytes = (stack_allocator::aligned_size<Args>() + ...);
}
meta.return_size_bytes = sizeof(Return);
meta.return_size_bytes = stack_allocator::aligned_size<Return>();
meta.argc = info.argc;
storage.operator_metadata.push_back(meta);

View File

@ -43,6 +43,10 @@ namespace blt::gp
{
auto& [program, current_pop, current_stats, config, random] = args;
BLT_ASSERT_MSG(config.elites <= current_pop.get_individuals().size(), ("Not enough individuals in population (" +
std::to_string(current_pop.get_individuals().size()) +
") for requested amount of elites (" + std::to_string(config.elites) + ")").c_str());
if (config.elites > 0 && current_pop.get_individuals().size() >= config.elites)
{
static thread_local tracked_vector<std::pair<std::size_t, double>> values;
@ -132,13 +136,13 @@ namespace blt::gp
// auto old_parent_val = parent_fitness.load(std::memory_order_relaxed);
// while (!parent_fitness.compare_exchange_weak(old_parent_val, old_parent_val + parent_val, std::memory_order_relaxed,
// std::memory_order_relaxed))
// std::memory_order_relaxed))
// {
// }
// auto old_child_val = child_fitness.load(std::memory_order_relaxed);
// while (!child_fitness.compare_exchange_weak(old_child_val, old_child_val + child_val, std::memory_order_relaxed,
// std::memory_order_relaxed))
// std::memory_order_relaxed))
// {
// }

View File

@ -28,6 +28,7 @@
#include <blt/meta/meta.h>
#include <blt/gp/fwdecl.h>
#include <blt/gp/util/trackers.h>
#include <blt/gp/allocator.h>
#include <utility>
#include <stdexcept>
#include <cstdlib>
@ -46,11 +47,19 @@ namespace blt::gp
class stack_allocator
{
constexpr static blt::size_t PAGE_SIZE = 0x100;
constexpr static blt::size_t MAX_ALIGNMENT = 8;
template <typename T>
using NO_REF_T = std::remove_cv_t<std::remove_reference_t<T>>;
using Allocator = aligned_allocator;
// todo remove this once i fix all the broken references
struct detail
{
static constexpr size_t aligned_size(const size_t size) noexcept
{
return (size + (gp::detail::MAX_ALIGNMENT - 1)) & ~(gp::detail::MAX_ALIGNMENT - 1);
}
};
public:
static Allocator& get_allocator();
@ -74,19 +83,14 @@ namespace blt::gp
};
template <typename T>
static inline constexpr size_t aligned_size() noexcept
static constexpr size_t aligned_size() noexcept
{
const auto bytes = aligned_size(sizeof(NO_REF_T<T>));
const auto bytes = detail::aligned_size(sizeof(NO_REF_T<T>));
if constexpr (blt::gp::detail::has_func_drop_v<T>)
return bytes + sizeof(size_t*);
return bytes + aligned_size<size_t*>();
return bytes;
}
static inline constexpr blt::size_t aligned_size(blt::size_t size) noexcept
{
return (size + (MAX_ALIGNMENT - 1)) & ~(MAX_ALIGNMENT - 1);
}
stack_allocator() = default;
stack_allocator(const stack_allocator& copy)
@ -138,7 +142,7 @@ namespace blt::gp
bytes_stored += bytes;
}
void copy_from(blt::u8* data, blt::size_t bytes)
void copy_from(const u8* data, const size_t bytes)
{
if (bytes == 0 || data == nullptr)
return;
@ -148,7 +152,7 @@ namespace blt::gp
bytes_stored += bytes;
}
void copy_to(blt::u8* data, blt::size_t bytes)
void copy_to(u8* data, const size_t bytes) const
{
if (bytes == 0 || data == nullptr)
return;
@ -158,64 +162,65 @@ namespace blt::gp
template <typename T, typename NO_REF = NO_REF_T<T>>
void push(const T& t)
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
auto ptr = allocate_bytes_for_size(sizeof(NO_REF));
static_assert(std::is_trivially_copyable_v<NO_REF>, "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= gp::detail::MAX_ALIGNMENT, "Type alignment must not be greater than the max alignment!");
const auto ptr = allocate_bytes_for_size(aligned_size<NO_REF>());
std::memcpy(ptr, &t, sizeof(NO_REF));
}
template <typename T, typename NO_REF = NO_REF_T<T>>
T pop()
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
constexpr auto size = aligned_size(sizeof(NO_REF));
static_assert(std::is_trivially_copyable_v<NO_REF>, "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= gp::detail::MAX_ALIGNMENT, "Type alignment must not be greater than the max alignment!");
constexpr auto size = aligned_size<NO_REF>();
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < size)
BLT_ABORT("Not enough bytes left to pop!");
if (bytes_stored < size)
BLT_ABORT("Not enough bytes left to pop!");
#endif
bytes_stored -= size;
return *reinterpret_cast<T*>(data_ + bytes_stored);
}
[[nodiscard]] blt::u8* from(blt::size_t bytes) const
[[nodiscard]] u8* from(const size_t bytes) const
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to reference " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to reference " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
#endif
return data_ + (bytes_stored - bytes);
}
template <typename T, typename NO_REF = NO_REF_T<T>>
T& from(blt::size_t bytes)
T& from(const size_t bytes)
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
return *reinterpret_cast<NO_REF*>(from(aligned_size(sizeof(NO_REF)) + bytes));
static_assert(alignof(NO_REF) <= gp::detail::MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
return *reinterpret_cast<NO_REF*>(from(aligned_size<NO_REF>() + bytes));
}
void pop_bytes(blt::size_t bytes)
void pop_bytes(const size_t bytes)
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to pop " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to pop " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
gp::detail::check_alignment(bytes);
#endif
bytes_stored -= bytes;
}
void transfer_bytes(stack_allocator& to, blt::size_t bytes)
void transfer_bytes(stack_allocator& to, const size_t aligned_bytes)
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to transfer " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
if (bytes_stored < aligned_bytes)
BLT_ABORT(("Not enough bytes in stack to transfer " + std::to_string(aligned_bytes) + " bytes requested but " + std::to_string(aligned_bytes) +
" bytes stored!").c_str());
gp::detail::check_alignment(aligned_bytes);
#endif
auto alg = aligned_size(bytes);
to.copy_from(*this, alg);
pop_bytes(alg);
to.copy_from(*this, aligned_bytes);
pop_bytes(aligned_bytes);
}
template <typename... Args>
@ -223,9 +228,8 @@ namespace blt::gp
{
if constexpr (sizeof...(Args) > 0)
{
blt::size_t offset = (stack_allocator::aligned_size(sizeof(NO_REF_T<Args>)) + ...) -
stack_allocator::aligned_size(sizeof(NO_REF_T<typename blt::meta::arg_helper<Args...>::First>));
((call_drop<Args>(offset), offset -= stack_allocator::aligned_size(sizeof(NO_REF_T<Args>))), ...);
size_t offset = (aligned_size<NO_REF_T<Args>>() + ...) - aligned_size<NO_REF_T<typename meta::arg_helper<Args...>::First>>();
((call_drop<Args>(offset), offset -= aligned_size<NO_REF_T<Args>>()), ...);
}
}
@ -234,14 +238,14 @@ namespace blt::gp
return bytes_stored == 0;
}
[[nodiscard]] blt::ptrdiff_t remaining_bytes_in_block() const noexcept
[[nodiscard]] ptrdiff_t remaining_bytes_in_block() const noexcept
{
return static_cast<blt::ptrdiff_t>(size_ - bytes_stored);
return static_cast<ptrdiff_t>(size_ - bytes_stored);
}
[[nodiscard]] blt::ptrdiff_t bytes_in_head() const noexcept
[[nodiscard]] ptrdiff_t bytes_in_head() const noexcept
{
return static_cast<blt::ptrdiff_t>(bytes_stored);
return static_cast<ptrdiff_t>(bytes_stored);
}
[[nodiscard]] size_data_t size() const noexcept
@ -255,18 +259,18 @@ namespace blt::gp
return data;
}
void reserve(blt::size_t bytes)
void reserve(const size_t bytes)
{
if (bytes > size_)
expand_raw(bytes);
}
[[nodiscard]] blt::size_t stored() const
[[nodiscard]] size_t stored() const
{
return bytes_stored;
}
[[nodiscard]] blt::size_t internal_storage_size() const
[[nodiscard]] size_t internal_storage_size() const
{
return size_;
}
@ -277,15 +281,16 @@ namespace blt::gp
}
private:
void expand(blt::size_t bytes)
void expand(const size_t bytes)
{
//bytes = to_nearest_page_size(bytes);
expand_raw(bytes);
}
void expand_raw(blt::size_t bytes)
void expand_raw(const size_t bytes)
{
auto new_data = static_cast<blt::u8*>(get_allocator().allocate(bytes));
// auto aligned = detail::aligned_size(bytes);
const auto new_data = static_cast<u8*>(get_allocator().allocate(bytes));
if (bytes_stored > 0)
std::memcpy(new_data, data_, bytes_stored);
get_allocator().deallocate(data_, size_);
@ -293,40 +298,42 @@ namespace blt::gp
size_ = bytes;
}
static size_t to_nearest_page_size(blt::size_t bytes) noexcept
static size_t to_nearest_page_size(const size_t bytes) noexcept
{
constexpr static blt::size_t MASK = ~(PAGE_SIZE - 1);
constexpr static size_t MASK = ~(PAGE_SIZE - 1);
return (bytes & MASK) + PAGE_SIZE;
}
void* get_aligned_pointer(blt::size_t bytes) noexcept
[[nodiscard]] void* get_aligned_pointer(const size_t bytes) const noexcept
{
if (data_ == nullptr)
return nullptr;
blt::size_t remaining_bytes = remaining_bytes_in_block();
size_t remaining_bytes = remaining_bytes_in_block();
auto* pointer = static_cast<void*>(data_ + bytes_stored);
return std::align(MAX_ALIGNMENT, bytes, pointer, remaining_bytes);
return std::align(gp::detail::MAX_ALIGNMENT, bytes, pointer, remaining_bytes);
}
void* allocate_bytes_for_size(blt::size_t bytes)
void* allocate_bytes_for_size(const size_t aligned_bytes)
{
auto used_bytes = aligned_size(bytes);
auto aligned_ptr = get_aligned_pointer(used_bytes);
#if BLT_DEBUG_LEVEL > 0
gp::detail::check_alignment(aligned_bytes);
#endif
auto aligned_ptr = get_aligned_pointer(aligned_bytes);
if (aligned_ptr == nullptr)
{
expand(size_ + used_bytes);
aligned_ptr = get_aligned_pointer(used_bytes);
expand(size_ + aligned_bytes);
aligned_ptr = get_aligned_pointer(aligned_bytes);
}
if (aligned_ptr == nullptr)
throw std::bad_alloc();
bytes_stored += used_bytes;
bytes_stored += aligned_bytes;
return aligned_ptr;
}
template <typename T>
inline void call_drop(blt::size_t offset)
void call_drop(const size_t offset)
{
if constexpr (detail::has_func_drop_v<T>)
if constexpr (blt::gp::detail::has_func_drop_v<T>)
{
from<NO_REF_T<T>>(offset).drop();
}

View File

@ -35,17 +35,37 @@ namespace blt::gp
struct op_container_t
{
op_container_t(const size_t type_size, const operator_id id, const bool is_value):
type_size(type_size), id(id), is_value(is_value), has_drop(false)
m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(false)
{}
op_container_t(const size_t type_size, const operator_id id, const bool is_value, const bool has_drop):
type_size(type_size), id(id), is_value(is_value), has_drop(has_drop)
m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(has_drop)
{}
size_t type_size;
operator_id id;
bool is_value;
bool has_drop;
[[nodiscard]] auto type_size() const
{
return m_type_size;
}
[[nodiscard]] auto id() const
{
return m_id;
}
[[nodiscard]] auto is_value() const
{
return m_is_value;
}
[[nodiscard]] bool has_drop() const
{
return m_has_drop;
}
private:
size_t m_type_size;
operator_id m_id;
bool m_is_value;
bool m_has_drop;
};
class evaluation_context
@ -83,42 +103,37 @@ namespace blt::gp
values.reset();
values.insert(copy.values);
// operations.reserve(copy.operations.size());
//
// auto copy_it = copy.operations.begin();
// auto op_it = operations.begin();
//
// for (; op_it != operations.end(); ++op_it)
// {
// if (op_it->has_drop)
// {
//
// }
// if (copy_it == copy.operations.end())
// break;
// *op_it = *copy_it;
// if (copy_it->has_drop)
// {
//
// }
// ++copy_it;
// }
// const auto op_it_cpy = op_it;
// for (;op_it != operations.end(); ++op_it)
// {
// if (op_it->has_drop)
// {
//
// }
// }
// operations.erase(op_it_cpy, operations.end());
// for (; copy_it != copy.operations.end(); ++copy_it)
// operations.emplace_back(*copy_it);
operations.clear();
operations.reserve(copy.operations.size());
operations.insert(operations.begin(), copy.operations.begin(), copy.operations.end());
auto copy_it = copy.operations.begin();
auto op_it = operations.begin();
for (; op_it != operations.end(); ++op_it)
{
if (op_it->has_drop())
{
}
if (copy_it == copy.operations.end())
break;
*op_it = *copy_it;
if (copy_it->has_drop())
{
}
++copy_it;
}
const auto op_it_cpy = op_it;
for (;op_it != operations.end(); ++op_it)
{
if (op_it->has_drop())
{
}
}
operations.erase(op_it_cpy, operations.end());
for (; copy_it != copy.operations.end(); ++copy_it)
operations.emplace_back(*copy_it);
}
tree_t(tree_t&& move) = default;
@ -213,36 +228,36 @@ namespace blt::gp
blt::ptrdiff_t find_parent(blt::gp::gp_program& program, blt::ptrdiff_t start) const;
// valid for [begin, end)
static blt::size_t total_value_bytes(detail::const_op_iter_t begin, detail::const_op_iter_t end)
static size_t total_value_bytes(const detail::const_op_iter_t begin, const detail::const_op_iter_t end)
{
blt::size_t total = 0;
for (auto it = begin; it != end; it++)
size_t total = 0;
for (auto it = begin; it != end; ++it)
{
if (it->is_value)
total += stack_allocator::aligned_size(it->type_size);
if (it->is_value())
total += it->type_size();
}
return total;
}
[[nodiscard]] blt::size_t total_value_bytes(blt::size_t begin, blt::size_t end) const
[[nodiscard]] size_t total_value_bytes(const size_t begin, const size_t end) const
{
return total_value_bytes(operations.begin() + static_cast<blt::ptrdiff_t>(begin),
operations.begin() + static_cast<blt::ptrdiff_t>(end));
}
[[nodiscard]] blt::size_t total_value_bytes(blt::size_t begin) const
[[nodiscard]] size_t total_value_bytes(const size_t begin) const
{
return total_value_bytes(operations.begin() + static_cast<blt::ptrdiff_t>(begin), operations.end());
}
[[nodiscard]] blt::size_t total_value_bytes() const
[[nodiscard]] size_t total_value_bytes() const
{
return total_value_bytes(operations.begin(), operations.end());
}
private:
tracked_vector<op_container_t> operations;
blt::gp::stack_allocator values;
stack_allocator values;
detail::eval_func_t* func;
};

View File

@ -46,9 +46,9 @@ namespace blt::gp
type() = default;
template<typename T>
static type make_type(type_id id)
static type make_type(const type_id id)
{
return type(sizeof(T), id, blt::type_string<T>());
return type(stack_allocator::aligned_size<T>(), id, blt::type_string<T>());
}
[[nodiscard]] inline blt::size_t size() const

@ -1 +1 @@
Subproject commit 1798980ac6829d5d79c162325a2162aa42917958
Subproject commit 8133553ed87f705c890c5becda710f72f3ddccb9

View File

@ -60,7 +60,7 @@ namespace blt::gp
tree_generator.pop();
auto& info = args.program.get_operator_info(top.id);
tree.get_operations().emplace_back(
args.program.get_typesystem().get_type(info.return_type).size(),
top.id,

View File

@ -40,8 +40,8 @@ namespace blt::gp
size_t total = 0;
for (auto it = begin; it != end; ++it)
{
if (it->is_value)
total += stack_allocator::aligned_size(it->type_size);
if (it->is_value())
total += it->type_size();
}
return total;
}
@ -157,14 +157,14 @@ namespace blt::gp
blt::size_t c2_found_bytes = c2.get_values().size().total_used_bytes;
blt::size_t c1_expected_bytes = std::accumulate(c1.get_operations().begin(), c1.get_operations().end(), 0ul,
[](const auto& v1, const auto& v2) {
if (v2.is_value)
return v1 + stack_allocator::aligned_size(v2.type_size);
if (v2.is_value())
return v1 + v2.type_size();
return v1;
});
blt::size_t c2_expected_bytes = std::accumulate(c2.get_operations().begin(), c2.get_operations().end(), 0ul,
[](const auto& v1, const auto& v2) {
if (v2.is_value)
return v1 + stack_allocator::aligned_size(v2.type_size);
if (v2.is_value())
return v1 + v2.type_size();
return v1;
});
if (c1_found_bytes != c1_expected_bytes || c2_found_bytes != c2_expected_bytes)
@ -191,7 +191,7 @@ namespace blt::gp
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())
while (!allow_terminal_selection && program.get_operator_info(c1_ops[crossover_point].id()).argc.is_terminal())
{
if (counter >= config.max_crossover_tries)
return {};
@ -201,13 +201,13 @@ namespace blt::gp
size_t attempted_point = 0;
const auto& crossover_point_type = program.get_operator_info(c1_ops[crossover_point].id);
const auto& crossover_point_type = program.get_operator_info(c1_ops[crossover_point].id());
const operator_info_t* attempted_point_type = nullptr;
for (counter = 0; counter < config.max_crossover_tries; counter++)
{
attempted_point = program.get_random().get_size_t(1ul, c2_ops.size());
attempted_point_type = &program.get_operator_info(c2_ops[attempted_point].id);
attempted_point_type = &program.get_operator_info(c2_ops[attempted_point].id());
if (!allow_terminal_selection && attempted_point_type->argc.is_terminal())
continue;
if (crossover_point_type.return_type == attempted_point_type->return_type)
@ -239,14 +239,14 @@ namespace blt::gp
ptrdiff_t point = 0;
while (true)
{
auto& current_op_type = program.get_operator_info(t.get_operations()[point].id);
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);
auto& parent_type = program.get_operator_info(t.get_operations()[parent].id());
if (type && *type != parent_type.return_type)
return {};
return {{parent, parent_type}};
@ -299,7 +299,7 @@ namespace blt::gp
auto begin_point = static_cast<blt::ptrdiff_t>(node);
auto end_point = c.find_endpoint(program, begin_point);
auto begin_operator_id = ops_r[begin_point].id;
auto begin_operator_id = ops_r[begin_point].id();
const auto& type_info = program.get_operator_info(begin_operator_id);
auto begin_itr = ops_r.begin() + begin_point;
@ -337,8 +337,8 @@ namespace blt::gp
for (const auto& op : c.get_operations())
{
if (op.is_value)
bytes_expected += stack_allocator::aligned_size(op.type_size);
if (op.is_value())
bytes_expected += op.type_size();
}
if (bytes_expected != bytes_size)
@ -352,14 +352,14 @@ namespace blt::gp
auto copy = c;
try
{
const auto& result = copy.evaluate();
const auto& result = copy.evaluate(*static_cast<char*>(detail::debug::context_ptr));
blt::black_box(result);
} catch (...)
{
std::cout << "This occurred at point " << begin_point << " ending at (old) " << end_point << "\n";
std::cout << "our root type is " << ops_r[begin_point].id << " with size " << stack_allocator::aligned_size(ops_r[begin_point].type_size)
std::cout << "our root type is " << ops_r[begin_point].id() << " with size " << ops_r[begin_point].type_size()
<< "\n";
std::cout << "now Named: " << (program.get_name(ops_r[begin_point].id) ? *program.get_name(ops_r[begin_point].id) : "Unnamed") << "\n";
std::cout << "now Named: " << (program.get_name(ops_r[begin_point].id()) ? *program.get_name(ops_r[begin_point].id()) : "Unnamed") << "\n";
std::cout << "Was named: " << (program.get_name(begin_operator_id) ? *program.get_name(begin_operator_id) : "Unnamed") << "\n";
//std::cout << "Parent:" << std::endl;
//p.print(program, std::cout, false, true);
@ -422,9 +422,9 @@ namespace blt::gp
{
// this is going to be evil >:3
const auto& node = ops[c_node];
if (!node.is_value)
if (!node.is_value())
{
auto& current_func_info = program.get_operator_info(ops[c_node].id);
auto& current_func_info = program.get_operator_info(ops[c_node].id());
operator_id random_replacement = program.get_random().select(
program.get_type_non_terminals(current_func_info.return_type.id));
auto& replacement_func_info = program.get_operator_info(random_replacement);
@ -483,8 +483,8 @@ namespace blt::gp
blt::size_t expected_bytes = std::accumulate(ops.begin(),
ops.end(), 0ul,
[](const auto& v1, const auto& v2) {
if (v2.is_value)
return v1 + stack_allocator::aligned_size(v2.type_size);
if (v2.is_value())
return v1 + v2.type_size();
return v1;
});
if (found_bytes != expected_bytes)
@ -545,7 +545,7 @@ namespace blt::gp
};
}
#if BLT_DEBUG_LEVEL >= 2
if (!c.check(program, nullptr))
if (!c.check(program, detail::debug::context_ptr))
{
std::cout << "Parent: " << std::endl;
c_copy.print(program, std::cout, false, true);
@ -559,7 +559,7 @@ namespace blt::gp
break;
case mutation_operator::SUB_FUNC:
{
auto& current_func_info = program.get_operator_info(ops[c_node].id);
auto& current_func_info = program.get_operator_info(ops[c_node].id());
// need to:
// mutate the current function.
@ -640,7 +640,7 @@ namespace blt::gp
});
#if BLT_DEBUG_LEVEL >= 2
if (!c.check(program, nullptr))
if (!c.check(program, detail::debug::context_ptr))
{
std::cout << "Parent: " << std::endl;
p.print(program, std::cout, false, true);
@ -656,7 +656,7 @@ namespace blt::gp
break;
case mutation_operator::JUMP_FUNC:
{
auto& info = program.get_operator_info(ops[c_node].id);
auto& info = program.get_operator_info(ops[c_node].id());
blt::size_t argument_index = -1ul;
for (const auto& [index, v] : blt::enumerate(info.argument_types))
{
@ -703,7 +703,7 @@ namespace blt::gp
vals.copy_from(storage_ptr, for_bytes + after_bytes);
#if BLT_DEBUG_LEVEL >= 2
if (!c.check(program, nullptr))
if (!c.check(program, detail::debug::context_ptr))
{
std::cout << "Parent: " << std::endl;
p.print(program, std::cout, false, true);
@ -717,7 +717,7 @@ namespace blt::gp
break;
case mutation_operator::COPY:
{
auto& info = program.get_operator_info(ops[c_node].id);
auto& info = program.get_operator_info(ops[c_node].id());
blt::size_t pt = -1ul;
blt::size_t pf = -1ul;
for (const auto& [index, v] : blt::enumerate(info.argument_types))
@ -803,7 +803,7 @@ namespace blt::gp
}
#if BLT_DEBUG_LEVEL >= 2
if (!c.check(program, nullptr))
if (!c.check(program, detail::debug::context_ptr))
{
std::cout << "Parent: " << std::endl;
p.print(program, std::cout, false, true);

View File

@ -72,14 +72,14 @@ namespace blt::gp
// reverse the order of the stack
for (const auto& v : operations)
{
if (v.is_value)
copy.transfer_bytes(reversed, v.type_size);
if (v.is_value())
copy.transfer_bytes(reversed, v.type_size());
}
}
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 info = program.get_operator_info(v.id());
const 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)
{
@ -92,10 +92,10 @@ namespace blt::gp
if (print_literals)
{
create_indent(out, indent, pretty_print);
if (program.is_operator_ephemeral(v.id))
if (program.is_operator_ephemeral(v.id()))
{
program.get_print_func(v.id)(out, reversed);
reversed.pop_bytes(stack_allocator::aligned_size(v.type_size));
program.get_print_func(v.id())(out, reversed);
reversed.pop_bytes(v.type_size());
} else
out << name;
out << return_type << end_indent(pretty_print);
@ -153,7 +153,7 @@ namespace blt::gp
for (const auto& op : operations_stack)
{
if (op.is_value)
if (op.is_value())
value_stack.push_back(1);
}
@ -162,7 +162,7 @@ namespace blt::gp
auto operation = operations_stack.back();
// keep the last value in the stack on the process stack stored in the eval context, this way it can be accessed easily.
operations_stack.pop_back();
if (operation.is_value)
if (operation.is_value())
{
auto d = value_stack.back();
depth = std::max(depth, d);
@ -171,54 +171,54 @@ namespace blt::gp
continue;
}
size_t local_depth = 0;
for (size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
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();
}
value_stack.push_back(local_depth + 1);
operations_stack.emplace_back(operation.type_size, operation.id, true);
operations_stack.emplace_back(operation.type_size(), operation.id(), true);
}
return depth;
}
blt::ptrdiff_t tree_t::find_endpoint(gp_program& program, blt::ptrdiff_t index) const
ptrdiff_t tree_t::find_endpoint(gp_program& program, ptrdiff_t start) const
{
blt::i64 children_left = 0;
i64 children_left = 0;
do
{
const auto& type = program.get_operator_info(operations[index].id);
const auto& type = program.get_operator_info(operations[start].id());
// this is a child to someone
if (children_left != 0)
children_left--;
if (type.argc.argc > 0)
children_left += type.argc.argc;
index++;
start++;
} while (children_left > 0);
return index;
return start;
}
// this function doesn't work!
blt::ptrdiff_t tree_t::find_parent(gp_program& program, blt::ptrdiff_t index) const
ptrdiff_t tree_t::find_parent(gp_program& program, ptrdiff_t start) const
{
blt::i64 children_left = 0;
i64 children_left = 0;
do
{
if (index == 0)
if (start == 0)
return 0;
const auto& type = program.get_operator_info(operations[index].id);
const auto& type = program.get_operator_info(operations[start].id());
if (type.argc.argc > 0)
children_left -= type.argc.argc;
children_left++;
if (children_left <= 0)
break;
--index;
--start;
} while (true);
return index;
return start;
}
bool tree_t::check(gp_program& program, void* context) const
@ -228,8 +228,8 @@ namespace blt::gp
for (const auto& op : get_operations())
{
if (op.is_value)
bytes_expected += stack_allocator::aligned_size(op.type_size);
if (op.is_value())
bytes_expected += op.type_size();
}
if (bytes_expected != bytes_size)
@ -252,24 +252,24 @@ namespace blt::gp
for (const auto& operation : iterate(operations).rev())
{
if (operation.is_value)
if (operation.is_value())
{
value_stack.transfer_bytes(values_process, operation.type_size);
total_produced += stack_allocator::aligned_size(operation.type_size);
value_stack.transfer_bytes(values_process, operation.type_size());
total_produced += operation.type_size();
continue;
}
auto& info = program.get_operator_info(operation.id);
auto& info = program.get_operator_info(operation.id());
for (auto& arg : info.argument_types)
total_consumed += stack_allocator::aligned_size(program.get_typesystem().get_type(arg).size());
program.get_operator_info(operation.id).func(context, values_process, values_process);
total_produced += stack_allocator::aligned_size(program.get_typesystem().get_type(info.return_type).size());
total_consumed += program.get_typesystem().get_type(arg).size();
program.get_operator_info(operation.id()).func(context, values_process, values_process);
total_produced += program.get_typesystem().get_type(info.return_type).size();
}
auto v1 = results.values.bytes_in_head();
auto v2 = static_cast<blt::ptrdiff_t>(stack_allocator::aligned_size(operations.front().type_size));
const auto v1 = results.values.bytes_in_head();
const auto v2 = static_cast<ptrdiff_t>(operations.front().type_size());
if (v1 != v2)
{
auto vd = std::abs(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,
std::abs(static_cast<blt::ptrdiff_t>(total_produced) - static_cast<blt::ptrdiff_t>(total_consumed)));
@ -278,7 +278,7 @@ namespace blt::gp
return true;
}
void tree_t::find_child_extends(gp_program& program, tracked_vector<child_t>& vec, blt::size_t parent_node, blt::size_t argc) const
void tree_t::find_child_extends(gp_program& program, tracked_vector<child_t>& vec, const size_t parent_node, const size_t argc) const
{
while (vec.size() < argc)
{
@ -287,8 +287,8 @@ namespace blt::gp
if (current_point == 0)
{
// first child.
prev = {static_cast<blt::ptrdiff_t>(parent_node + 1),
find_endpoint(program, static_cast<blt::ptrdiff_t>(parent_node + 1))};
prev = {static_cast<ptrdiff_t>(parent_node + 1),
find_endpoint(program, static_cast<ptrdiff_t>(parent_node + 1))};
vec.push_back(prev);
continue;
} else

View File

@ -0,0 +1,164 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include "../examples/symbolic_regression.h"
#include <fstream>
#include <array>
#include <blt/profiling/profiler_v2.h>
static const auto SEED_FUNC = [] { return std::random_device()(); };
std::array crossover_chances = {1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0};
std::array mutation_chances = {0.0, 0.1, 0.2, 0.9, 1.0};
std::array reproduction_chances = {0.0, 1.0, 0.1, 0.9};
std::array elite_amounts = {0, 2, 10, 50};
std::array population_sizes = {50, 500, 5000};
blt::gp::prog_config_t best_config;
double best_fitness = 0;
void run(const blt::gp::prog_config_t& config)
{
// the config is copied into the gp_system so changing the config will not change the runtime of the program.
blt::gp::example::symbolic_regression_t regression{SEED_FUNC, config};
BLT_START_INTERVAL("Symbolic Regression", "Setup Operations");
regression.setup_operations();
BLT_END_INTERVAL("Symbolic Regression", "Setup Operations");
BLT_START_INTERVAL("Symbolic Regression", "Generate Initial Population");
regression.generate_initial_population();
BLT_END_INTERVAL("Symbolic Regression", "Generate Initial Population");
BLT_START_INTERVAL("Symbolic Regression", "Total Generation Loop");
BLT_DEBUG("Begin Generation Loop");
auto& program = regression.get_program();
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");
BLT_START_INTERVAL("Symbolic Regression", "Create Next Generation");
program.create_next_generation();
BLT_END_INTERVAL("Symbolic Regression", "Create Next Generation");
BLT_TRACE("Move to next generation");
BLT_START_INTERVAL("Symbolic Regress", "Move Next Generation");
program.next_generation();
BLT_END_INTERVAL("Symbolic Regress", "Move Next Generation");
BLT_TRACE("Evaluate Fitness");
BLT_START_INTERVAL("Symbolic Regress", "Evaluate Fitness");
program.evaluate_fitness();
BLT_END_INTERVAL("Symbolic Regress", "Evaluate Fitness");
BLT_START_INTERVAL("Symbolic Regress", "Fitness Print");
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));
BLT_END_INTERVAL("Symbolic Regress", "Fitness Print");
#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_END_INTERVAL("Symbolic Regression", "Total Generation Loop");
const auto best = program.get_best_individuals<1>();
if (best[0].get().fitness.adjusted_fitness > best_fitness)
{
best_fitness = best[0].get().fitness.adjusted_fitness;
best_config = config;
}
}
int main()
{
std::stringstream results;
for (const auto crossover_chance : crossover_chances)
{
for (const auto mutation_chance : mutation_chances)
{
for (const auto reproduction_chance : reproduction_chances)
{
for (const auto elite_amount : elite_amounts)
{
for (const auto population_sizes : population_sizes)
{
blt::gp::prog_config_t config = blt::gp::prog_config_t()
.set_initial_min_tree_size(2)
.set_initial_max_tree_size(6)
.set_elite_count(elite_amount)
.set_crossover_chance(crossover_chance)
.set_mutation_chance(mutation_chance)
.set_reproduction_chance(reproduction_chance)
.set_max_generations(50)
.set_pop_size(population_sizes)
.set_thread_count(0);
BLT_INFO_STREAM << "Run: Crossover (";
BLT_INFO_STREAM << crossover_chance;
BLT_INFO_STREAM << ") Mutation (";
BLT_INFO_STREAM << mutation_chance;
BLT_INFO_STREAM << ") Reproduction (";
BLT_INFO_STREAM << reproduction_chance;
BLT_INFO_STREAM << ") Elite (";
BLT_INFO_STREAM << elite_amount;
BLT_INFO_STREAM << ") Population Size (";
BLT_INFO_STREAM << population_sizes;
BLT_INFO_STREAM << ")" << "\n";
run(config);
results << "Run: Crossover (";
results << crossover_chance;
results << ") Mutation (";
results << mutation_chance;
results << ") Reproduction (";
results << reproduction_chance;
results << ") Elite (";
results << elite_amount;
results << ") Population Size (";
results << population_sizes;
results << ")" << std::endl;
BLT_WRITE_PROFILE(results, "Symbolic Regression");
results << std::endl;
}
}
}
}
}
std::cout << results.str() << std::endl;
std::cout << "Best Configuration is: " << std::endl;
std::cout << "\tCrossover: " << best_config.crossover_chance << std::endl;
std::cout << "\tMutation: " << best_config.mutation_chance << std::endl;
std::cout << "\tReproduction: " << best_config.reproduction_chance << std::endl;
std::cout << "\tElites: " << best_config.elites << std::endl;
std::cout << "\tPopulation Size: " << best_config.population_size << std::endl;
std::cout << std::endl;
}