diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d3abf6..acae504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 () \ No newline at end of file diff --git a/examples/src/rice_classification.cpp b/examples/src/rice_classification.cpp index 30ee6f6..2f57ed3 100644 --- a/examples/src/rice_classification.cpp +++ b/examples/src/rice_classification.cpp @@ -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(training_case); switch (training_case.type) { diff --git a/examples/src/symbolic_regression.cpp b/examples/src/symbolic_regression.cpp index ecddb24..4886e58 100644 --- a/examples/src/symbolic_regression.cpp +++ b/examples/src/symbolic_regression.cpp @@ -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(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(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 builder{}; - builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x); - program.set_operations(builder.grab()); -} diff --git a/examples/symbolic_regression.h b/examples/symbolic_regression.h index 3d6ed4b..9f465ab 100644 --- a/examples/symbolic_regression.h +++ b/examples/symbolic_regression.h @@ -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(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(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 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() { diff --git a/include/blt/gp/allocator.h b/include/blt/gp/allocator.h index ec6f95a..5e9a3b2 100644 --- a/include/blt/gp/allocator.h +++ b/include/blt/gp/allocator.h @@ -20,12 +20,29 @@ #define BLT_GP_ALLOCATOR_H #include +#include #include #include #include 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 using tracked_vector = std::vector>; #else - template + template using tracked_vector = std::vector; #endif } diff --git a/include/blt/gp/fwdecl.h b/include/blt/gp/fwdecl.h index 967df9d..313bf40 100644 --- a/include/blt/gp/fwdecl.h +++ b/include/blt/gp/fwdecl.h @@ -85,6 +85,18 @@ namespace blt::gp using const_op_iter_t = tracked_vector::const_iterator; using op_iter_t = tracked_vector::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(static_cast(&context)) +#else + #define BLT_GP_UPDATE_CONTEXT(context) +#endif } diff --git a/include/blt/gp/program.h b/include/blt/gp/program.h index 0d613f4..0084a23 100644 --- a/include/blt/gp/program.h +++ b/include/blt/gp/program.h @@ -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() + ...); } - meta.return_size_bytes = sizeof(Return); + meta.return_size_bytes = stack_allocator::aligned_size(); meta.argc = info.argc; storage.operator_metadata.push_back(meta); diff --git a/include/blt/gp/selection.h b/include/blt/gp/selection.h index bd1da61..e7eee65 100644 --- a/include/blt/gp/selection.h +++ b/include/blt/gp/selection.h @@ -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> 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)) // { // } diff --git a/include/blt/gp/stack.h b/include/blt/gp/stack.h index e8b5646..93de54d 100644 --- a/include/blt/gp/stack.h +++ b/include/blt/gp/stack.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -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 using NO_REF_T = std::remove_cv_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 - static inline constexpr size_t aligned_size() noexcept + static constexpr size_t aligned_size() noexcept { - const auto bytes = aligned_size(sizeof(NO_REF_T)); + const auto bytes = detail::aligned_size(sizeof(NO_REF_T)); if constexpr (blt::gp::detail::has_func_drop_v) - return bytes + sizeof(size_t*); + return bytes + aligned_size(); 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 > void push(const T& t) { - static_assert(std::is_trivially_copyable_v && "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, "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()); std::memcpy(ptr, &t, sizeof(NO_REF)); } template > T pop() { - static_assert(std::is_trivially_copyable_v && "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, "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(); #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(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 > - T& from(blt::size_t bytes) + T& from(const size_t bytes) { static_assert(std::is_trivially_copyable_v && "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(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(from(aligned_size() + 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 @@ -223,9 +228,8 @@ namespace blt::gp { if constexpr (sizeof...(Args) > 0) { - blt::size_t offset = (stack_allocator::aligned_size(sizeof(NO_REF_T)) + ...) - - stack_allocator::aligned_size(sizeof(NO_REF_T::First>)); - ((call_drop(offset), offset -= stack_allocator::aligned_size(sizeof(NO_REF_T))), ...); + size_t offset = (aligned_size>() + ...) - aligned_size::First>>(); + ((call_drop(offset), offset -= aligned_size>()), ...); } } @@ -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(size_ - bytes_stored); + return static_cast(size_ - bytes_stored); } - [[nodiscard]] blt::ptrdiff_t bytes_in_head() const noexcept + [[nodiscard]] ptrdiff_t bytes_in_head() const noexcept { - return static_cast(bytes_stored); + return static_cast(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(get_allocator().allocate(bytes)); + // auto aligned = detail::aligned_size(bytes); + const auto new_data = static_cast(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(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 - inline void call_drop(blt::size_t offset) + void call_drop(const size_t offset) { - if constexpr (detail::has_func_drop_v) + if constexpr (blt::gp::detail::has_func_drop_v) { from>(offset).drop(); } diff --git a/include/blt/gp/tree.h b/include/blt/gp/tree.h index 97e842c..f27e9b5 100644 --- a/include/blt/gp/tree.h +++ b/include/blt/gp/tree.h @@ -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(begin), operations.begin() + static_cast(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(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 operations; - blt::gp::stack_allocator values; + stack_allocator values; detail::eval_func_t* func; }; diff --git a/include/blt/gp/typesystem.h b/include/blt/gp/typesystem.h index 20ec7ba..875a857 100644 --- a/include/blt/gp/typesystem.h +++ b/include/blt/gp/typesystem.h @@ -46,9 +46,9 @@ namespace blt::gp type() = default; template - static type make_type(type_id id) + static type make_type(const type_id id) { - return type(sizeof(T), id, blt::type_string()); + return type(stack_allocator::aligned_size(), id, blt::type_string()); } [[nodiscard]] inline blt::size_t size() const diff --git a/lib/blt b/lib/blt index 1798980..8133553 160000 --- a/lib/blt +++ b/lib/blt @@ -1 +1 @@ -Subproject commit 1798980ac6829d5d79c162325a2162aa42917958 +Subproject commit 8133553ed87f705c890c5becda710f72f3ddccb9 diff --git a/src/generators.cpp b/src/generators.cpp index 1c59a87..803a24f 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -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, diff --git a/src/transformers.cpp b/src/transformers.cpp index 496062d..96d899f 100644 --- a/src/transformers.cpp +++ b/src/transformers.cpp @@ -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(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(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); diff --git a/src/tree.cpp b/src/tree.cpp index 3160009..e5c16a6 100644 --- a/src/tree.cpp +++ b/src/tree.cpp @@ -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(stack_allocator::aligned_size(operations.front().type_size)); + const auto v1 = results.values.bytes_in_head(); + const auto v2 = static_cast(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(total_produced) - static_cast(total_consumed))); @@ -278,7 +278,7 @@ namespace blt::gp return true; } - void tree_t::find_child_extends(gp_program& program, tracked_vector& vec, blt::size_t parent_node, blt::size_t argc) const + void tree_t::find_child_extends(gp_program& program, tracked_vector& 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(parent_node + 1), - find_endpoint(program, static_cast(parent_node + 1))}; + prev = {static_cast(parent_node + 1), + find_endpoint(program, static_cast(parent_node + 1))}; vec.push_back(prev); continue; } else diff --git a/tests/symbolic_regression_test.cpp b/tests/symbolic_regression_test.cpp new file mode 100644 index 0000000..44950c8 --- /dev/null +++ b/tests/symbolic_regression_test.cpp @@ -0,0 +1,164 @@ +/* + * + * 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 . + */ +#include "../examples/symbolic_regression.h" + +#include +#include +#include + +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; +}