From 93188a61200d7d66302363057b8e74deac510029 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 29 Jan 2024 21:33:22 -0500 Subject: [PATCH] gp --- .gitmodules | 3 + include/config.h | 27 ++++ include/functions.h | 13 ++ include/gp.h | 110 +++++++++++++ include/image.h | 3 +- libraries/stats | 1 + src/functions.cpp | 11 +- src/gp.cpp | 194 ++++++++++++++++++++++ src/main.cpp | 387 ++++++++++++++------------------------------ 9 files changed, 477 insertions(+), 272 deletions(-) create mode 100644 include/config.h create mode 100644 include/gp.h create mode 160000 libraries/stats create mode 100644 src/gp.cpp diff --git a/.gitmodules b/.gitmodules index c055cad..e8ee7df 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libraries/BLT-With-Graphics-Template"] path = libraries/BLT-With-Graphics-Template url = https://git.tpgc.me/tri11paragon/BLT-With-Graphics-Template.git +[submodule "libraries/stats"] + path = libraries/stats + url = https://github.com/kthohr/stats.git diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..3e269e7 --- /dev/null +++ b/include/config.h @@ -0,0 +1,27 @@ +/* + * + * Copyright (C) 2024 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 . + */ + +#ifndef GP_IMAGE_TEST_CONFIG_H +#define GP_IMAGE_TEST_CONFIG_H + +#include + +inline constexpr blt::i32 MAX_DEPTH = 12; +inline constexpr blt::i32 width = 256, height = 256; + +#endif //GP_IMAGE_TEST_CONFIG_H diff --git a/include/functions.h b/include/functions.h index 4820c60..091e8d3 100644 --- a/include/functions.h +++ b/include/functions.h @@ -244,6 +244,19 @@ inline static allowed_funcs intersection_comp(const allowed_funcs +//bool isNan(F f) +//{ +// return f == std::numeric_limits::quiet_NaN() || f == std::numeric_limits::signaling_NaN(); +//} + +template +bool isInf(F f) +{ + return f == std::numeric_limits::infinity() || f == -std::numeric_limits::infinity(); +} + #endif //GP_IMAGE_TEST_FUNCTIONS_H diff --git a/include/gp.h b/include/gp.h new file mode 100644 index 0000000..c2b484c --- /dev/null +++ b/include/gp.h @@ -0,0 +1,110 @@ +/* + * + * Copyright (C) 2024 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 . + */ + +#ifndef GP_IMAGE_TEST_GP_H +#define GP_IMAGE_TEST_GP_H + +#include +#include +#include + +struct node; +class tree; + +node* createNode(function_t type); +void destroyNode(node* n); + +struct node +{ + friend tree; + private: + // only valid up to the argc + std::array sub_nodes{nullptr}; + size_t argc = 0; + + function_t type = function_t::ADD; + data_t data{}; + + std::optional img; + + void grow(bool use_terminals); + + void full(bool use_terminals); + + void populate_node(size_t i, std::mt19937_64& engine, const allowed_funcs& allowed_args, bool use_terminal); + + void print_tree(); + + public: + explicit node(function_t type): type(type) + { + static thread_local std::random_device dev; + static thread_local std::mt19937_64 engine{dev()}; + static thread_local std::uniform_real_distribution dist(0.0f, 1.0f); + if (type == function_t::SCALAR) + data[0] = dist(engine); + else if (type == function_t::COLOR) + { + for (auto& v : data) + v = dist(engine); + } + } + + static node* construct_random_tree(); + + void evaluate(); + + void evaluate_tree() + { + for (size_t i = 0; i < argc; i++) + sub_nodes[i]->evaluate_tree(); + evaluate(); + } + + void printTree() + { + print_tree(); + std::cout << std::endl; + } + + bool hasImage() + { + return img.has_value(); + } + + image& getImage() + { + return img.value(); + } + + ~node() + { + for (auto* node : sub_nodes) + destroyNode(node); + } +}; + +struct node_deleter +{ + void operator()(node* ptr) + { + destroyNode(ptr); + } +}; + +#endif //GP_IMAGE_TEST_GP_H diff --git a/include/image.h b/include/image.h index e0d94b2..c0f4cf5 100644 --- a/include/image.h +++ b/include/image.h @@ -27,8 +27,7 @@ #include "blt/std/allocator.h" #include "blt/math/vectors.h" #include - -inline constexpr blt::i32 width = 256, height = 256; +#include using image_data_t = std::array; inline blt::area_allocator img_allocator; diff --git a/libraries/stats b/libraries/stats new file mode 160000 index 0000000..f8dcb15 --- /dev/null +++ b/libraries/stats @@ -0,0 +1 @@ +Subproject commit f8dcb15ae51cce7142b239805745a0de56aa509f diff --git a/src/functions.cpp b/src/functions.cpp index 2b8f837..a82ee85 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #define FUNCTION_COORD() \ int xi = static_cast(x); \ @@ -234,16 +235,18 @@ void f_if(image& img, float x, float y, blt::size_t argc, const image** argv, co double on_data(const double* data, blt::size_t len) { - static float NEGATION = -1; + const static float NEGATION = 0; willbruh results(data, len); switch (results->error) { case 0: - return results->estimate; + if (!std::isnan(results->estimate)) + return results->estimate; + return 0; case 1: BLT_INFO("Somehow we have sent more than 5000 values!"); - case -1: - case -3: + return 0; + default: return 0; case -2: // should never consider this a valid solution / punish for having large sections of no difference diff --git a/src/gp.cpp b/src/gp.cpp new file mode 100644 index 0000000..4735d70 --- /dev/null +++ b/src/gp.cpp @@ -0,0 +1,194 @@ +/* + * + * Copyright (C) 2024 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 +#include +#include +#include +#include +#include + +blt::area_allocator node_allocator; + +node* createNode(function_t type) +{ + auto* n = node_allocator.allocate(1); + node_allocator.construct(n, type); + return n; +} + +void destroyNode(node* n) +{ + node_allocator.destroy(n); + node_allocator.deallocate(n, 1); +} + +void node::grow(bool use_terminals) +{ + auto min_children = function_arg_min_map[to_underlying(type)]; + auto max_children = function_arg_max_map[to_underlying(type)]; + + static thread_local std::random_device dev; + static thread_local std::mt19937_64 engine{dev()}; + std::uniform_int_distribution dist(min_children, max_children); + static std::uniform_int_distribution choice(0, 1); + + argc = dist(engine); + if (argc == 0) + return; + const auto& allowed_args_args = function_arg_allowed_map[to_underlying(type)]; + for (size_t i = 0; i < argc; i++) + { + // 50/50 chance to either use a terminal or use from the function list. + populate_node(i, engine, allowed_args_args[i], choice(engine) || use_terminals); + } +} + +void node::full(bool use_terminals) +{ + static thread_local std::random_device dev; + static thread_local std::mt19937_64 engine{dev()}; + + argc = function_arg_max_map[to_underlying(type)]; + if (argc == 0) + return; + + const auto& allowed_args_args = function_arg_allowed_map[to_underlying(type)]; + for (size_t i = 0; i < argc; i++) + populate_node(i, engine, allowed_args_args[i], use_terminals); +} + +void node::populate_node(size_t i, std::mt19937_64& engine, const allowed_funcs& allowed_args, bool use_terminal) +{ + if (use_terminal) + { + auto terminals = intersection(allowed_args, FUNC_ALLOW_TERMINALS_SET); + if (terminals.empty()) + { + terminals = FUNC_ALLOW_TERMINALS; +// BLT_INFO("%s:", function_name_map[to_underlying(type)].c_str()); +// for (auto v : allowed_args) +// BLT_INFO(function_name_map[to_underlying(v)]); + } + std::uniform_int_distribution select(0, terminals.size() - 1); + sub_nodes[i] = createNode(terminals[select(engine)]); + } else + { + auto non_terminals = intersection_comp(allowed_args, FUNC_ALLOW_TERMINALS_SET); + if (non_terminals.empty()) + { + BLT_WARN("Empty non-terminals set! Filling from terminals!"); + std::uniform_int_distribution select(0, allowed_args.size() - 1); + sub_nodes[i] = createNode(allowed_args[select(engine)]); + } else + { + std::uniform_int_distribution select(0, non_terminals.size() - 1); + sub_nodes[i] = createNode(non_terminals[select(engine)]); + } + } +} + +void node::print_tree() +{ + if (argc > 0) + std::cout << "("; + if (argc > 0) + std::cout << function_name_map[to_underlying(type)] << " "; + else + { + if (type == function_t::SCALAR) + { + evaluate(); + std::cout << img->get().x() << " "; + } else if (type == function_t::COLOR) + { + evaluate(); + std::cout << '{' << img->get().x() << ", " << img->get().y() << ", " << img->get().z() << "} "; + } else + std::cout << function_name_map[to_underlying(type)] << " "; + } + for (size_t i = 0; i < argc; i++) + sub_nodes[i]->print_tree(); + if (argc > 0) + std::cout << ") "; +} + +node* node::construct_random_tree() +{ + static std::random_device dev; + static std::mt19937_64 engine{dev()}; + std::uniform_int_distribution choice(0, 1); + + static auto NON_TERMINALS = intersection_comp(FUNC_ALLOW_ANY, FUNC_ALLOW_TERMINALS_SET); + + std::uniform_int_distribution select(0, static_cast(NON_TERMINALS.size()) - 1); + + node* n = createNode(NON_TERMINALS[select(engine)]); + + std::queue> grow_queue; + size_t current_depth = 0; + grow_queue.emplace(n, current_depth); + while (!grow_queue.empty()) + { + auto front = grow_queue.front(); + if (front.second != current_depth) + current_depth++; + + if (choice(engine)) + front.first->grow(front.second >= MAX_DEPTH); + else + front.first->full(front.second >= MAX_DEPTH); + + for (size_t i = 0; i < front.first->argc; i++) + grow_queue.emplace(front.first->sub_nodes[i], current_depth + 1); + grow_queue.pop(); + } + + return n; +} + +void node::evaluate() +{ + img = image{}; + std::array sub_node_images{nullptr}; + for (size_t i = 0; i < argc; i++) + { + BLT_ASSERT(sub_nodes[i] != nullptr && sub_nodes[i]->img.has_value() && "Node must have evaluated children!"); + sub_node_images[i] = &sub_nodes[i]->img.value(); + } +#define FUNC_DEFINE(NAME, MIN_ARGS, MAX_ARGS, FUNC, ...) case function_t::NAME: { \ + if (FUNC_ALLOW_TERMINALS_SET.contains(function_t::NAME)){ \ + FUNC(img.value(), 0, 0, argc, const_cast(sub_node_images.data()), data); \ + } else { \ + for (blt::i32 y = 0; y < height; y++) { \ + for (blt::i32 x = 0; x < width; x++) { \ + FUNC(img.value(), static_cast(x), static_cast(y), argc, const_cast(sub_node_images.data()), data); \ + } \ + } \ + } \ + } \ + break; + + switch (type) + { + FUNC_FUNCTIONS + default: + BLT_WARN("How did we get here?"); + break; + } +#undef FUNC_DEFINE +} diff --git a/src/main.cpp b/src/main.cpp index daf42c3..3aa0863 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "blt/gfx/renderer/resource_manager.h" #include "blt/gfx/renderer/batch_2d_renderer.h" @@ -13,264 +12,157 @@ #include #include #include -#include +#include +#include blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; blt::gfx::batch_renderer_2d renderer_2d(resources); -constexpr blt::i32 MAX_DEPTH = 17; - -struct node; - -blt::area_allocator node_allocator; - -node* createNode(function_t type) +class tree { - auto* n = node_allocator.allocate(1); - node_allocator.construct(n, type); - return n; -} - - -void destroyNode(node* n) -{ - node_allocator.destroy(n); - node_allocator.deallocate(n, 1); -} - -class tree; - -struct node -{ - friend tree; - private: - // only valid up to the argc - std::array sub_nodes{nullptr}; - size_t argc = 0; - - function_t type = function_t::ADD; - data_t data{}; - - std::optional img; - - void grow(bool use_terminals) - { - auto min_children = function_arg_min_map[to_underlying(type)]; - auto max_children = function_arg_max_map[to_underlying(type)]; - - static thread_local std::random_device dev; - static thread_local std::mt19937_64 engine{dev()}; - std::uniform_int_distribution dist(min_children, max_children); - static std::uniform_int_distribution choice(0, 1); - - argc = dist(engine); - if (argc == 0) - return; - const auto& allowed_args_args = function_arg_allowed_map[to_underlying(type)]; - for (size_t i = 0; i < argc; i++) - { - // 50/50 chance to either use a terminal or use from the function list. - populate_node(i, engine, allowed_args_args[i], choice(engine) || use_terminals); - } - } - - void full(bool use_terminals) - { - static thread_local std::random_device dev; - static thread_local std::mt19937_64 engine{dev()}; - - argc = function_arg_max_map[to_underlying(type)]; - if (argc == 0) - return; - - const auto& allowed_args_args = function_arg_allowed_map[to_underlying(type)]; - for (size_t i = 0; i < argc; i++) - populate_node(i, engine, allowed_args_args[i], use_terminals); - } - - inline void populate_node(size_t i, std::mt19937_64& engine, const allowed_funcs& allowed_args, bool use_terminal) - { - if (use_terminal) - { - auto terminals = intersection(allowed_args, FUNC_ALLOW_TERMINALS_SET); - if (terminals.empty()) - { - terminals = FUNC_ALLOW_TERMINALS; -// BLT_INFO("%s:", function_name_map[to_underlying(type)].c_str()); -// for (auto v : allowed_args) -// BLT_INFO(function_name_map[to_underlying(v)]); - } - std::uniform_int_distribution select(0, terminals.size() - 1); - sub_nodes[i] = createNode(terminals[select(engine)]); - } else - { - auto non_terminals = intersection_comp(allowed_args, FUNC_ALLOW_TERMINALS_SET); - if (non_terminals.empty()) - { - BLT_WARN("Empty non-terminals set! Filling from terminals!"); - std::uniform_int_distribution select(0, allowed_args.size() - 1); - sub_nodes[i] = createNode(allowed_args[select(engine)]); - } else - { - std::uniform_int_distribution select(0, non_terminals.size() - 1); - sub_nodes[i] = createNode(non_terminals[select(engine)]); - } - } - } - - void print_tree() - { - if (argc > 0) - std::cout << "("; - if (argc > 0) - std::cout << function_name_map[to_underlying(type)] << " "; - else - { - if (type == function_t::SCALAR) - { - evaluate(); - std::cout << img->get().x() << " "; - } else if (type == function_t::COLOR) - { - evaluate(); - std::cout << '{' << img->get().x() << ", " << img->get().y() << ", " << img->get().z() << "} "; - } else - std::cout << function_name_map[to_underlying(type)] << " "; - } - for (size_t i = 0; i < argc; i++) - sub_nodes[i]->print_tree(); - if (argc > 0) - std::cout << ") "; - } - public: - explicit node(function_t type): type(type) + struct crossover_result_t { - static thread_local std::random_device dev; - static thread_local std::mt19937_64 engine{dev()}; - static thread_local std::uniform_real_distribution dist(0.0f, 1.0f); - if (type == function_t::SCALAR) - data[0] = dist(engine); - else if (type == function_t::COLOR) + std::unique_ptr c1, c2; + }; + private: + std::unique_ptr root = nullptr; + + explicit tree(node* root): root(root) + {} + + void normalize_image() + { + float mx = 0, my = 0, mz = 0; + float sx = 0, sy = 0, sz = 0; + for (auto& v : root->getImage().getData()) { - for (auto& v : data) - v = dist(engine); + for (int i = 0; i < 3; i++) + { + if (std::isnan(v[i])) + { + v[i] = 0.0f; + } + } + mx = std::max(v.x(), mx); + my = std::max(v.y(), my); + mz = std::max(v.z(), mz); + sx = std::min(v.x(), sx); + sy = std::min(v.y(), sy); + sz = std::min(v.z(), sz); + } + for (auto& v : root->getImage().getData()) + { + if (mx - sx != 0) + v[0] = (v.x() - sx) / (mx - sx); + if (my - sy != 0) + v[1] = (v.y() - sy) / (my - sy); + if (mz - sz != 0) + v[2] = (v.z() - sz) / (mz - sz); } } - static node* construct_random_tree() + node* select_random_child() { static std::random_device dev; static std::mt19937_64 engine{dev()}; - std::uniform_int_distribution choice(0, 1); + auto d = depth(); + std::uniform_int_distribution depth_dist(0, static_cast(d)); + std::uniform_int_distribution select(0, 3); - static auto NON_TERMINALS = intersection_comp(FUNC_ALLOW_ANY, FUNC_ALLOW_TERMINALS_SET); + node* current = root.get(); - std::uniform_int_distribution select(0, NON_TERMINALS.size() - 1); - - node* n = createNode(NON_TERMINALS[select(engine)]); - - std::queue> grow_queue; - size_t current_depth = 0; - grow_queue.emplace(n, current_depth); - while (!grow_queue.empty()) + while (true) { - auto front = grow_queue.front(); - if (front.second != current_depth) - current_depth++; - - if (choice(engine)) - front.first->grow(front.second >= MAX_DEPTH); - else - front.first->full(front.second >= MAX_DEPTH); - - for (size_t i = 0; i < front.first->argc; i++) - grow_queue.emplace(front.first->sub_nodes[i], current_depth + 1); - grow_queue.pop(); + std::uniform_int_distribution children(0, current->argc - 1); + } + } + + public: + static std::unique_ptr construct_random_tree() + { + return std::make_unique(tree{node::construct_random_tree()}); + } + + blt::size_t depth() + { + // depth -> node + std::stack> stack; + blt::size_t max_depth = 0; + + stack.emplace(0, root.get()); + while (!stack.empty()) + { + auto top = stack.top(); + auto* node = top.second; + auto depth = top.first; + max_depth = std::max(max_depth, depth); + for (blt::size_t i = 0; i < node->argc; i++) + { + if (node->sub_nodes[i] != nullptr) + { + stack.emplace(depth + 1, node->sub_nodes[i]); + } + } + stack.pop(); } - return n; + return max_depth; + } + + static crossover_result_t crossover(tree* p1, tree* p2) + { + } void evaluate() { - img = image{}; - std::array sub_node_images{nullptr}; - for (size_t i = 0; i < argc; i++) - { - BLT_ASSERT(sub_nodes[i] != nullptr && sub_nodes[i]->img.has_value() && "Node must have evaluated children!"); - sub_node_images[i] = &sub_nodes[i]->img.value(); - } -#define FUNC_DEFINE(NAME, MIN_ARGS, MAX_ARGS, FUNC, ...) case function_t::NAME: { \ - if (FUNC_ALLOW_TERMINALS_SET.contains(function_t::NAME)){ \ - FUNC(img.value(), 0, 0, argc, const_cast(sub_node_images.data()), data); \ - } else { \ - for (blt::i32 y = 0; y < height; y++) { \ - for (blt::i32 x = 0; x < width; x++) { \ - FUNC(img.value(), static_cast(x), static_cast(y), argc, const_cast(sub_node_images.data()), data); \ - } \ - } \ - } \ - } \ - break; - - switch (type) - { - FUNC_FUNCTIONS - default: - BLT_WARN("How did we get here?"); - break; - } -#undef FUNC_DEFINE - } - - void evaluate_tree() - { - for (size_t i = 0; i < argc; i++) - sub_nodes[i]->evaluate_tree(); - evaluate(); - } - - void printTree() - { - print_tree(); - std::cout << std::endl; - } - - bool hasImage() - { - return img.has_value(); + root->evaluate_tree(); + normalize_image(); } image& getImage() { - return img.value(); + return root->getImage(); } - ~node() + bool hasImage() { - for (auto* node : sub_nodes) - destroyNode(node); + return root->hasImage(); + } + + bool hasTree() + { + return root != nullptr; + } + + float fitness() + { + return eval_DNF_SW(root->getImage()); + } + + void printTree() + { + root->printTree(); } }; -class tree -{ - public: - node* root; - - -}; - -node* root; +std::unique_ptr test_tree; blt::gfx::texture_gl2D* texture; -bool fucky(float f) +void print_bits(float f) { - return f == std::numeric_limits::quiet_NaN() || f == -std::numeric_limits::quiet_NaN() || - f == std::numeric_limits::infinity() || - f == -std::numeric_limits::infinity(); + auto u = blt::mem::type_cast(f); + for (size_t i = 31; i > 0; i--) + { + std::cout << ((u >> i) & 0x1); + if (i % 8 == 0) + std::cout << ", "; + else if (i % 4 == 0) + std::cout << " "; + } + std::cout << std::endl; } void init() @@ -293,59 +185,22 @@ void update(std::int32_t w, std::int32_t h) if (ImGui::Button("Regenerate")) { BLT_INFO("Regen tree"); - destroyNode(root); - root = node::construct_random_tree(); - root->evaluate_tree(); - BLT_INFO("Preprocess"); - - float mx = 0, my = 0, mz = 0; - float sx = 0, sy = 0, sz = 0; - if (root->hasImage()) - { - for (auto& v : root->getImage().getData()) - { - //v = v.normalize(); - for (int i = 0; i < 3; i++) - v[i] = std::abs(v[i]); - mx = std::max(v.x(), mx); - my = std::max(v.y(), my); - mz = std::max(v.z(), mz); - sx = std::min(v.x(), sx); - sy = std::min(v.y(), sy); - sz = std::min(v.z(), sz); - } - for (auto& v : root->getImage().getData()) - { - if (mx - sx != 0) - v[0] = (v.x() - sx) / (mx - sx); - if (my - sy != 0) - v[1] = (v.y() - sy) / (my - sy); - if (mz - sz != 0) - v[2] = (v.z() - sz) / (mz - sz); - } - } - - + test_tree = tree::construct_random_tree(); + test_tree->evaluate(); BLT_INFO("Uploading"); - //delete texture; - if (root->hasImage()) - texture->upload((void*) root->getImage().getData().data(), GL_RGB, 0, 0, 0, -1, -1, GL_FLOAT); - //texture->upload((void*) test.data(), GL_RGBA, 0, 0, 0, width, height, GL_UNSIGNED_BYTE); - //texture->upload(file); - //texture = new blt::gfx::texture_gl2D(file); - resources.set("img", texture); + texture->upload((void*) test_tree->getImage().getData().data(), GL_RGB, 0, 0, 0, -1, -1, GL_FLOAT); } if (ImGui::Button("Display")) { - if (root) - root->printTree(); + if (test_tree->hasTree()) + test_tree->printTree(); } if (ImGui::Button("Eval")) { - if (root && root->hasImage()) - BLT_DEBUG(eval_DNF_SW(root->getImage())); + if (test_tree->hasImage()) + BLT_DEBUG(test_tree->fitness()); } auto lw = 512.0f;