Brett 2024-01-29 21:33:22 -05:00
parent a8e5673555
commit 93188a6120
9 changed files with 477 additions and 272 deletions

3
.gitmodules vendored
View File

@ -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

27
include/config.h Normal file
View File

@ -0,0 +1,27 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef GP_IMAGE_TEST_CONFIG_H
#define GP_IMAGE_TEST_CONFIG_H
#include <blt/std/types.h>
inline constexpr blt::i32 MAX_DEPTH = 12;
inline constexpr blt::i32 width = 256, height = 256;
#endif //GP_IMAGE_TEST_CONFIG_H

View File

@ -244,6 +244,19 @@ inline static allowed_funcs<function_t> intersection_comp(const allowed_funcs<fu
// distribution from normality (DFN)
float eval_DNF_SW(const image& img);
float eval_DNF_KS(const image& img);
//template<typename F>
//bool isNan(F f)
//{
// return f == std::numeric_limits<F>::quiet_NaN() || f == std::numeric_limits<F>::signaling_NaN();
//}
template<typename F>
bool isInf(F f)
{
return f == std::numeric_limits<F>::infinity() || f == -std::numeric_limits<F>::infinity();
}
#endif //GP_IMAGE_TEST_FUNCTIONS_H

110
include/gp.h Normal file
View File

@ -0,0 +1,110 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef GP_IMAGE_TEST_GP_H
#define GP_IMAGE_TEST_GP_H
#include <config.h>
#include <functions.h>
#include <random>
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<node*, MAX_ARGS> sub_nodes{nullptr};
size_t argc = 0;
function_t type = function_t::ADD;
data_t data{};
std::optional<image> img;
void grow(bool use_terminals);
void full(bool use_terminals);
void populate_node(size_t i, std::mt19937_64& engine, const allowed_funcs<function_t>& 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<float> 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

View File

@ -27,8 +27,7 @@
#include "blt/std/allocator.h"
#include "blt/math/vectors.h"
#include <variant>
inline constexpr blt::i32 width = 256, height = 256;
#include <config.h>
using image_data_t = std::array<blt::vec3, width * height>;
inline blt::area_allocator<image_data_t, 32> img_allocator;

1
libraries/stats Submodule

@ -0,0 +1 @@
Subproject commit f8dcb15ae51cce7142b239805745a0de56aa509f

View File

@ -20,6 +20,7 @@
#include <blt/gfx/stb/stb_perlin.h>
#include <blt/std/memory_util.h>
#include <extern.h>
#include <cmath>
#define FUNCTION_COORD() \
int xi = static_cast<int>(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:
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

194
src/gp.cpp Normal file
View File

@ -0,0 +1,194 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include <gp.h>
#include <blt/std/allocator.h>
#include <blt/std/logging.h>
#include <functions.h>
#include <random>
#include <queue>
blt::area_allocator<node, 32000> 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<blt::i32> dist(min_children, max_children);
static std::uniform_int_distribution<int> 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<function_t>& 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<blt::size_t> 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<blt::size_t> select(0, allowed_args.size() - 1);
sub_nodes[i] = createNode(allowed_args[select(engine)]);
} else
{
std::uniform_int_distribution<blt::size_t> 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<int> choice(0, 1);
static auto NON_TERMINALS = intersection_comp(FUNC_ALLOW_ANY, FUNC_ALLOW_TERMINALS_SET);
std::uniform_int_distribution<int> select(0, static_cast<int>(NON_TERMINALS.size()) - 1);
node* n = createNode(NON_TERMINALS[select(engine)]);
std::queue<std::pair<node*, size_t>> 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<image*, MAX_ARGS> 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<const image**>(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<float>(x), static_cast<float>(y), argc, const_cast<const image**>(sub_node_images.data()), data); \
} \
} \
} \
} \
break;
switch (type)
{
FUNC_FUNCTIONS
default:
BLT_WARN("How did we get here?");
break;
}
#undef FUNC_DEFINE
}

View File

@ -3,7 +3,6 @@
#include <blt/gfx/window.h>
#include <blt/gfx/state.h>
#include <blt/gfx/stb/stb_image.h>
#include <blt/std/hashmap.h>
#include <functions.h>
#include "blt/gfx/renderer/resource_manager.h"
#include "blt/gfx/renderer/batch_2d_renderer.h"
@ -13,264 +12,157 @@
#include <random>
#include <queue>
#include <stack>
#include <extern.h>
#include <config.h>
#include <gp.h>
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, 32000> 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<node*, MAX_ARGS> sub_nodes{nullptr};
size_t argc = 0;
function_t type = function_t::ADD;
data_t data{};
std::optional<image> 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<blt::i32> dist(min_children, max_children);
static std::uniform_int_distribution<int> 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<function_t>& 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<blt::size_t> 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<blt::size_t> select(0, allowed_args.size() - 1);
sub_nodes[i] = createNode(allowed_args[select(engine)]);
} else
{
std::uniform_int_distribution<blt::size_t> 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<float> dist(0.0f, 1.0f);
if (type == function_t::SCALAR)
data[0] = dist(engine);
else if (type == function_t::COLOR)
std::unique_ptr<tree> c1, c2;
};
private:
std::unique_ptr<node, node_deleter> root = nullptr;
explicit tree(node* root): root(root)
{}
void normalize_image()
{
for (auto& v : data)
v = dist(engine);
float mx = 0, my = 0, mz = 0;
float sx = 0, sy = 0, sz = 0;
for (auto& v : root->getImage().getData())
{
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<int> choice(0, 1);
auto d = depth();
std::uniform_int_distribution depth_dist(0, static_cast<int>(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<int> select(0, NON_TERMINALS.size() - 1);
node* n = createNode(NON_TERMINALS[select(engine)]);
std::queue<std::pair<node*, size_t>> 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<size_t> children(0, current->argc - 1);
}
}
return n;
public:
static std::unique_ptr<tree> construct_random_tree()
{
return std::make_unique<tree>(tree{node::construct_random_tree()});
}
blt::size_t depth()
{
// depth -> node
std::stack<std::pair<blt::size_t, node*>> 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 max_depth;
}
static crossover_result_t crossover(tree* p1, tree* p2)
{
}
void evaluate()
{
img = image{};
std::array<image*, MAX_ARGS> 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<const image**>(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<float>(x), static_cast<float>(y), argc, const_cast<const image**>(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<tree> test_tree;
blt::gfx::texture_gl2D* texture;
bool fucky(float f)
void print_bits(float f)
{
return f == std::numeric_limits<float>::quiet_NaN() || f == -std::numeric_limits<float>::quiet_NaN() ||
f == std::numeric_limits<float>::infinity() ||
f == -std::numeric_limits<float>::infinity();
auto u = blt::mem::type_cast<blt::u32>(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;