Compare commits

..

107 Commits

Author SHA1 Message Date
Brett 94014ef48a silly gonna try something 2025-05-01 00:40:59 -04:00
Brett 914ad4fc49 restore working state 2025-05-01 00:35:59 -04:00
Brett 36fb9b86cc transfer should be based on silly thing that does the thing (move or swap based on index vs size) 2025-04-29 22:51:26 -04:00
Brett b20cc2513a parker's emotion chip 2025-04-29 22:38:02 -04:00
Brett 6700161699 has a silly flaw 2025-04-29 21:48:05 -04:00
Brett f6d8073be9 some resolver 2025-04-29 13:42:35 -04:00
Brett 21ceb2322b start resolver 2025-04-29 01:02:17 -04:00
Brett ea5759cf1a docs and change 2025-04-29 00:25:58 -04:00
Brett 0251bf33f8 todo: consolidate thread locals 2025-04-28 15:30:05 -04:00
Brett 19504dd938 nonsense 2025-04-28 15:11:38 -04:00
Brett cb16be1bae testing variants 2025-04-20 16:21:02 -04:00
Brett f573aaf92f hi 2025-04-19 22:26:34 -04:00
Brett 27ecb2b46d idk what changed but it's still broken 2025-04-19 17:58:12 -04:00
Brett dc49f0b33a uwu 2025-04-18 22:09:49 -04:00
Brett 9ea6568cdb wow 2025-04-18 18:13:59 -04:00
Brett b634ea7268 why 2025-04-18 15:47:46 -04:00
Brett 487f771377 working on transformers 2025-04-18 13:34:32 -04:00
Brett b9c535f6c9 boy 2025-04-17 21:12:17 -04:00
Brett dcefa93d29 serial broken 2025-04-17 17:45:32 -04:00
Brett 29d8efe18f fix 2025-04-17 02:20:33 -04:00
Brett 0d299affdb why is it broken on nix? 2025-04-17 02:15:16 -04:00
Brett f89628615a uwu 2025-04-16 23:27:34 -04:00
Brett 81b35f20b9 writing 2025-04-15 22:18:44 -04:00
Brett 9bc234882a start 2025-04-15 02:21:14 -04:00
Brett 49c80c8f53 hi 2025-04-12 18:28:08 -04:00
Brett 373fdec6d2 silly 2025-04-12 16:22:37 -04:00
Brett fab86f8162 watching fun 2025-04-11 20:53:40 -04:00
Brett 62af2584d1 hello 2025-04-11 19:49:57 -04:00
Brett d14ccfd1da add ability to write trees to file 2025-04-08 01:50:23 -04:00
Brett ece76e1a91 puppy 2025-04-06 22:05:06 -04:00
Brett d8c7a27214 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-04-06 21:28:19 -04:00
Brett 51eb1711b9 why 2025-04-06 21:27:44 -04:00
Brett 2e09696a67 Fix bug with generators? 2025-04-05 17:14:16 -04:00
Brett 0dc083e095 fix issue with gp references 2025-04-04 18:53:35 -04:00
Brett 0eea2189e3 silly drop test update 2025-04-03 13:44:59 -04:00
Brett a742164bed clarafiy comment 2025-04-02 21:09:18 -04:00
Brett abb4cc26a4 update config 2025-04-02 18:48:11 -04:00
Brett d14b7ee7ce allow setting config 2025-04-02 18:46:27 -04:00
Brett 9cc4b9949b sorting 2025-04-02 01:07:52 -04:00
Brett 08e44fb4bd add ability to do steady state GPs. Untested currently. 2025-04-01 20:55:40 -04:00
Brett 577f3d613c silly 2025-03-31 17:47:58 -04:00
Brett 88957ce805 little bit more docs 2025-01-28 15:25:47 -05:00
Brett 0dadfb73a7 trying to track down where github is saying there is an error 2025-01-28 11:22:25 -05:00
Brett a58fe64c0e fix copy op, change selection behaviour 2025-01-21 21:20:16 -05:00
Brett 82c535dbd1 idk what is broken 2025-01-21 19:35:20 -05:00
Brett b79fc5a0f1 hello 2025-01-21 17:29:22 -05:00
Brett f83ad3b2f4 alloc tracker 2025-01-21 11:55:39 -05:00
Brett 100a4bfd39 small 2025-01-21 01:29:04 -05:00
Brett fa07017299 fix bug in debug mode 2025-01-20 23:15:20 -05:00
Brett d457abc92f hey lifetimes!!! 2025-01-20 19:45:41 -05:00
Brett 1a070ab5f2 fixed one bug, pretty sure there are more 2025-01-20 17:08:46 -05:00
Brett 4cfbdc08ec test 2025-01-20 15:45:32 -05:00
Brett b3153511ee fixed transformations but now need to fix bugs 2025-01-20 01:32:29 -05:00
Brett de853370be wow 2025-01-18 18:04:35 -05:00
Brett 1430e2f9a8 fixed drop being called on normals, still need to move tree functions 2025-01-18 13:12:36 -05:00
Brett ccc6abaa79 subtree swap now in tree class, realized im not sure why it isn't working. 2025-01-17 17:06:02 -05:00
Brett 0e5d8dfd25 almost have ephemeral drop working, probably missing an implicit copy 2025-01-17 15:35:15 -05:00
Brett 87d0a46552 mem tests 2025-01-17 12:18:59 -05:00
Brett 316f973fd8 wow 2025-01-16 19:47:22 -05:00
Brett f7d28d7a65 working on fixing memorys 2025-01-16 15:24:58 -05:00
Brett 44571f5402 new tree methods 2025-01-16 02:27:47 -05:00
Brett 9eff9ea8ef wow there is something wrong, check debug mode! 2025-01-15 21:44:38 -05:00
Brett 8594c44bae drop is now being called correctly. 2025-01-15 21:10:35 -05:00
Brett 8917fc12f7 what chanced? 2025-01-15 12:25:48 -05:00
Brett 6ab3c8c500 eph 2025-01-15 02:09:34 -05:00
Brett f9aba7a7a6 wow! 2025-01-14 15:32:57 -05:00
Brett db0c7da8e7 start adding functions for ref 2025-01-14 14:55:17 -05:00
Brett 32a83e725c move evaluation function into tree class, can now call 'make_execution_lambda' to make the lambda previously found in the operator_builder class 2025-01-14 14:00:55 -05:00
Brett 1e9442bbd4 threading is silly 2025-01-14 11:56:35 -05:00
Brett fca412ea29 wow 2025-01-13 19:43:41 -05:00
Brett 99b784835b remove broken state from test 2025-01-13 18:05:29 -05:00
Brett 0cf5450eb7 does this work? 2025-01-13 17:53:28 -05:00
Brett 053a34e30c finally push changes 2025-01-13 15:58:06 -05:00
Brett ca0f10b410 Begin working on adding drop function, add tests for many symbolic regression configs + performance 2025-01-11 18:25:55 -05:00
Brett a497a006ee last commit before swtiching to drop branch 2025-01-11 15:56:21 -05:00
Brett d19bc6b94b hello 2025-01-10 20:30:37 -05:00
Brett 82d36caecf provide a method for detecting copies inside tree 2025-01-10 20:10:30 -05:00
Brett 9fb3d78bca moving changes 2025-01-10 18:59:58 -05:00
Brett 5356be6649 crossover changes 2024-12-25 00:10:34 -05:00
Brett 3e0fe06017 things are working now. missing fitness reset was partial problem 2024-12-24 13:06:09 -05:00
Brett e1083426fc now both examples are in their own header file 2024-12-24 01:17:14 -05:00
Brett 946ddcc572 rice broken 2024-12-24 00:02:29 -05:00
Brett c6a2b5d324 this is a breaking patch 2024-12-23 00:17:02 -05:00
Brett e4ce3739f0 why? 2024-12-21 22:06:57 -05:00
Brett 094fa76b58
Update README.md 2024-10-04 16:32:15 -04:00
Brett f3027671f5
Create README.md 2024-10-04 16:06:37 -04:00
Brett 92760a217d
Create LICENSE 2024-10-04 15:10:57 -04:00
Brett 65f5fb3efa fix the tests:
probably going to take a break from this for a while
support will still be provided and i will likely return soon :3
2024-09-12 15:24:27 -04:00
Brett b4b7e8f454 uwu silly little crossover 2024-09-11 13:10:44 -04:00
Brett 15ccd0b615 transform 2024-09-10 18:40:24 -04:00
Brett 9cab2d3d4c continue work on new crossover 2024-09-10 15:47:44 -04:00
Brett 04fe36deb8 silly 2024-09-06 14:33:23 -04:00
Brett a5238bc798 silly 2024-09-06 13:46:07 -04:00
Brett 2b1f1c92ab working on main is bad 2024-09-06 12:40:08 -04:00
Brett 8b0b0dbefa Merge branch 'dev' 2024-09-06 12:30:14 -04:00
Brett 699806aeff meow? 2024-09-06 00:21:55 -04:00
Brett 0dfa4fea93 need new crossover 2024-09-05 02:20:03 -04:00
Brett 43e5ac46b9 remove testing 2024-09-04 22:54:51 -04:00
Brett 85e78df690 Merge branch 'dev' 2024-09-03 22:39:13 -04:00
Brett d69c45a82b Merge branch 'dev' 2024-09-01 22:27:33 -04:00
Brett 0da04237c7 Merge branch 'dev' 2024-08-31 22:44:44 -04:00
Brett 89ed1bd116 Merge branch 'shared' 2024-08-31 22:15:59 -04:00
Brett ea9721b4cc silly 2024-08-23 22:43:03 -04:00
Brett bd230f075f Merge branch 'dev' 2024-08-21 20:40:59 -04:00
Brett 914995fc82 Merge branch 'dev' 2024-08-19 21:03:31 -04:00
Brett 58681803e9 Merge branch 'dev' 2024-08-19 20:02:47 -04:00
Brett 76de033fe8 malloc is taking a good amount 2024-08-18 03:32:42 -04:00
65 changed files with 15683 additions and 5090 deletions

4
.gitignore vendored
View File

@ -1,4 +1,4 @@
cmake-build*/
\cmake-build*/
build/
out/
./cmake-build*/
@ -8,4 +8,4 @@ massif.*
callgrind.*
*.out.*
heaptrack.*
Rice_Cammeo_Osmancik.arff
vgcore.*

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Brett C++ Config 2" />
</state>
</component>

13
.idea/editor.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Class_0020and_0020struct_0020fields/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;11&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;class field&quot; /&gt;&lt;type Name=&quot;struct field&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;m_&quot; Suffix=&quot;&quot; Style=&quot;aa_bb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enums/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;3&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;enum&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AA_BB&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
</component>
</project>

View File

@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CppClassCanBeFinal" enabled="false" level="HINT" enabled_by_default="false" />
<inspection_tool class="CppDeclarationHidesUncapturedLocal" enabled="false" level="HINT" enabled_by_default="false" />
<inspection_tool class="CppTooWideScopeInitStatement" enabled="false" level="HINT" enabled_by_default="false" />
</profile>
</component>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

View File

@ -1,5 +1,33 @@
cmake_minimum_required(VERSION 3.25)
project(blt-gp VERSION 0.1.50)
macro(sanitizers target_name)
if (${ENABLE_ADDRSAN} MATCHES ON)
target_compile_options(${target_name} PRIVATE -fsanitize=address)
target_link_options(${target_name} PRIVATE -fsanitize=address)
endif ()
if (${ENABLE_UBSAN} MATCHES ON)
target_compile_options(${target_name} PRIVATE -fsanitize=undefined)
target_link_options(${target_name} PRIVATE -fsanitize=undefined)
endif ()
if (${ENABLE_TSAN} MATCHES ON)
target_compile_options(${target_name} PRIVATE -fsanitize=thread)
target_link_options(${target_name} PRIVATE -fsanitize=thread)
endif ()
endmacro()
macro(compile_options target_name)
if (NOT ${MOLD} STREQUAL MOLD-NOTFOUND)
target_compile_options(${target_name} PUBLIC -fuse-ld=mold)
endif ()
target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
target_link_options(${target_name} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
sanitizers(${target_name})
endmacro()
project(blt-gp VERSION 0.5.27)
include(CTest)
@ -9,7 +37,8 @@ option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF)
option(BUILD_EXAMPLES "Build example programs. This will build with CTest" OFF)
option(BUILD_GP_TESTS "Build test programs." OFF)
option(DEBUG_LEVEL "Enable debug features which prints extra information to the console, might slow processing down. [0, 3)" 0)
option(TRACK_ALLOCATIONS "Track total allocations. Can be accessed with blt::gp::tracker" OFF)
option(BLT_GP_DEBUG_CHECK_TREES "Enable checking of trees after every operation" OFF)
option(BLT_GP_DEBUG_TRACK_ALLOCATIONS "Track total allocations. Can be accessed with blt::gp::tracker" OFF)
set(CMAKE_CXX_STANDARD 17)
@ -18,6 +47,10 @@ find_package(Threads REQUIRED)
SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -g")
if (NOT ${CMAKE_BUILD_TYPE})
set(CMAKE_BUILD_TYPE Release)
endif ()
if (NOT TARGET BLT)
add_subdirectory(lib/blt)
endif ()
@ -27,8 +60,7 @@ file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(blt-gp ${PROJECT_BUILD_FILES})
target_compile_options(blt-gp PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
target_link_options(blt-gp PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
compile_options(blt-gp)
find_program(MOLD "mold")
@ -45,85 +77,52 @@ if (${TRACK_ALLOCATIONS})
target_compile_definitions(blt-gp PRIVATE BLT_TRACK_ALLOCATIONS=1)
endif ()
if (${ENABLE_ADDRSAN} MATCHES ON)
target_compile_options(blt-gp PRIVATE -fsanitize=address)
target_link_options(blt-gp PRIVATE -fsanitize=address)
endif ()
if (${ENABLE_UBSAN} MATCHES ON)
target_compile_options(blt-gp PRIVATE -fsanitize=undefined)
target_link_options(blt-gp PRIVATE -fsanitize=undefined)
endif ()
if (${ENABLE_TSAN} MATCHES ON)
target_compile_options(blt-gp PRIVATE -fsanitize=thread)
target_link_options(blt-gp PRIVATE -fsanitize=thread)
endif ()
macro(blt_add_project name source type)
project(${name}-${type})
#add_compile_options(-fuse-ld=mold)
add_executable(${name}-${type} ${source})
target_link_libraries(${name}-${type} PRIVATE BLT blt-gp Threads::Threads)
target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
compile_options(${name}-${type})
target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL})
if (${TRACK_ALLOCATIONS})
target_compile_definitions(${name}-${type} PRIVATE BLT_TRACK_ALLOCATIONS=1)
endif ()
if (${ENABLE_ADDRSAN} MATCHES ON)
target_compile_options(${name}-${type} PRIVATE -fsanitize=address)
target_link_options(${name}-${type} PRIVATE -fsanitize=address)
endif ()
add_test(NAME ${name}-${type} COMMAND ${name}-${type})
if (${ENABLE_UBSAN} MATCHES ON)
target_compile_options(${name}-${type} PRIVATE -fsanitize=undefined)
target_link_options(${name}-${type} PRIVATE -fsanitize=undefined)
endif ()
if (${ENABLE_TSAN} MATCHES ON)
target_compile_options(${name}-${type} PRIVATE -fsanitize=thread)
target_link_options(${name}-${type} PRIVATE -fsanitize=thread)
endif ()
add_test(NAME ${name} COMMAND ${name}-${type})
# set (passRegex "Pass" "Passed" "PASS" "PASSED")
# set (failRegex "WARN" "FAIL" "ERROR" "FATAL")
set(failRegex "\\[WARN\\]" "FAIL" "ERROR" "FATAL" "exception")
# set_property (TEST ${name} PROPERTY PASS_REGULAR_EXPRESSION "${passRegex}")
set_property(TEST ${name} PROPERTY FAIL_REGULAR_EXPRESSION "${failRegex}")
set_property(TEST ${name}-${type} PROPERTY FAIL_REGULAR_EXPRESSION "FAIL;ERROR;FATAL;exception")
project(blt-gp)
endmacro()
if (${BUILD_EXAMPLES})
blt_add_project(blt-symbolic-regression examples/symbolic_regression.cpp example)
blt_add_project(blt-rice-classification examples/rice_classification.cpp example)
blt_add_project(blt-symbolic-regression examples/src/symbolic_regression.cpp example)
blt_add_project(blt-rice-classification examples/src/rice_classification.cpp example)
endif ()
if (${BUILD_GP_TESTS})
blt_add_project(blt-stack tests/stack_tests.cpp test)
blt_add_project(blt-eval tests/evaluation_tests.cpp test)
blt_add_project(blt-order tests/order_tests.cpp test)
# blt_add_project(blt-stack tests/old/stack_tests.cpp test)
# blt_add_project(blt-eval tests/old/evaluation_tests.cpp test)
# blt_add_project(blt-order tests/old/order_tests.cpp test)
#blt_add_project(blt-destructor tests/destructor_test.cpp test)
blt_add_project(blt-gp1 tests/gp_test_1.cpp test)
blt_add_project(blt-gp2 tests/gp_test_2.cpp test)
blt_add_project(blt-gp3 tests/gp_test_3.cpp test)
blt_add_project(blt-gp4 tests/gp_test_4.cpp test)
blt_add_project(blt-gp5 tests/gp_test_5.cpp test)
blt_add_project(blt-gp6 tests/gp_test_6.cpp test)
blt_add_project(blt-gp7 tests/gp_test_7.cpp test)
# blt_add_project(blt-gp1 tests/old/gp_test_1.cpp test)
# blt_add_project(blt-gp2 tests/old/gp_test_2.cpp test)
# blt_add_project(blt-gp3 tests/old/gp_test_3.cpp test)
# blt_add_project(blt-gp4 tests/old/gp_test_4.cpp test)
# blt_add_project(blt-gp5 tests/old/gp_test_5.cpp test)
# 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)
blt_add_project(blt-drop tests/drop_test.cpp test)
blt_add_project(blt-drop-2-type tests/2_type_drop_test.cpp test)
blt_add_project(blt-serialization tests/serialization_test.cpp test)
endif ()

View File

@ -0,0 +1,230 @@
bool type_aware_crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2)
{
if (p1.size() < config.min_tree_size || p2.size() < config.min_tree_size)
return false;
tree_t::subtree_point_t point1, point2;
if (config.traverse)
{
point1 = p1.select_subtree_traverse(config.terminal_chance, config.depth_multiplier);
if (const auto val = p2.select_subtree_traverse(point1.type, config.max_crossover_tries, config.terminal_chance, config.depth_multiplier))
point2 = *val;
else
return false;
} else
{
point1 = p1.select_subtree(config.terminal_chance);
if (const auto val = p2.select_subtree(point1.type, config.max_crossover_tries, config.terminal_chance))
point2 = *val;
else
return false;
}
const auto& p1_operator = p1.get_operator(point1.pos);
const auto& p2_operator = p2.get_operator(point2.pos);
// If either is a terminal (value), just do normal subtree crossover
if (p1_operator.is_value() || p2_operator.is_value())
{
c1.swap_subtrees(point1, c2, point2);
return true;
}
const auto& p1_info = program.get_operator_info(p1_operator.id());
const auto& p2_info = program.get_operator_info(p2_operator.id());
// Find the child subtrees of both operators
thread_local tracked_vector<tree_t::child_t> children_data_p1;
thread_local tracked_vector<tree_t::child_t> children_data_p2;
children_data_p1.clear();
children_data_p2.clear();
p1.find_child_extends(children_data_p1, point1.pos, p1_info.argument_types.size());
p2.find_child_extends(children_data_p2, point2.pos, p2_info.argument_types.size());
// Check if all types are identical but possibly in different order
bool same_types_different_order = p1_info.argument_types.size() == p2_info.argument_types.size();
if (same_types_different_order)
{
// Create frequency counts of types in both operators
std::unordered_map<type_id, size_t> type_counts_p1;
std::unordered_map<type_id, size_t> type_counts_p2;
for (const auto& type : p1_info.argument_types)
type_counts_p1[type.id]++;
for (const auto& type : p2_info.argument_types)
type_counts_p2[type.id]++;
// Check if the type counts match
for (const auto& [type, count] : type_counts_p1)
{
if (type_counts_p2[type] != count)
{
same_types_different_order = false;
break;
}
}
}
if (same_types_different_order)
{
// Create a mapping from p1's argument positions to p2's positions
std::vector<size_t> arg_mapping(p1_info.argument_types.size(), (size_t)-1);
std::vector<bool> p2_used(p2_info.argument_types.size(), false);
// First pass: match exact types in order
for (size_t i = 0; i < p1_info.argument_types.size(); i++)
{
for (size_t j = 0; j < p2_info.argument_types.size(); j++)
{
if (!p2_used[j] && p1_info.argument_types[i].id == p2_info.argument_types[j].id)
{
arg_mapping[i] = j;
p2_used[j] = true;
break;
}
}
}
// Copy operators first
auto& c1_temp = tree_t::get_thread_local(program);
auto& c2_temp = tree_t::get_thread_local(program);
c1_temp.clear(program);
c2_temp.clear(program);
// Create new operators with the same return types
c1_temp.insert_operator({
program.get_typesystem().get_type(p2_info.return_type).size(),
p2_operator.id(),
program.is_operator_ephemeral(p2_operator.id()),
program.get_operator_flags(p2_operator.id())
});
c2_temp.insert_operator({
program.get_typesystem().get_type(p1_info.return_type).size(),
p1_operator.id(),
program.is_operator_ephemeral(p1_operator.id()),
program.get_operator_flags(p1_operator.id())
});
// Copy child subtrees according to the mapping
for (size_t i = 0; i < p1_info.argument_types.size(); i++)
{
auto& p1_child = children_data_p1[i];
auto& p2_child = children_data_p2[arg_mapping[i]];
tree_t p1_subtree(program);
tree_t p2_subtree(program);
p1.copy_subtree(tree_t::subtree_point_t(p1_child.start), p1_child.end, p1_subtree);
p2.copy_subtree(tree_t::subtree_point_t(p2_child.start), p2_child.end, p2_subtree);
c1_temp.insert_subtree(tree_t::subtree_point_t(c1_temp.size()), p2_subtree);
c2_temp.insert_subtree(tree_t::subtree_point_t(c2_temp.size()), p1_subtree);
}
// Replace the original subtrees with our new reordered ones
c1.replace_subtree(point1, c1_temp);
c2.replace_subtree(point2, c2_temp);
}
else
{
// If types don't match exactly, fall back to simple operator swap
// but we need to ensure the children are compatible
// Create new operators with swapped operators but appropriate children
auto& c1_temp = tree_t::get_thread_local(program);
auto& c2_temp = tree_t::get_thread_local(program);
c1_temp.clear(program);
c2_temp.clear(program);
c1_temp.insert_operator({
program.get_typesystem().get_type(p2_info.return_type).size(),
p2_operator.id(),
program.is_operator_ephemeral(p2_operator.id()),
program.get_operator_flags(p2_operator.id())
});
c2_temp.insert_operator({
program.get_typesystem().get_type(p1_info.return_type).size(),
p1_operator.id(),
program.is_operator_ephemeral(p1_operator.id()),
program.get_operator_flags(p1_operator.id())
});
// Create a mapping of which children we can reuse and which need to be regenerated
for (size_t i = 0; i < p2_info.argument_types.size(); i++)
{
const auto& needed_type = p2_info.argument_types[i];
bool found_match = false;
// Try to find a matching child from p1
for (size_t j = 0; j < p1_info.argument_types.size(); j++)
{
if (needed_type.id == p1_info.argument_types[j].id)
{
// Copy this child subtree from p1
auto& p1_child = children_data_p1[j];
tree_t p1_subtree(program);
p1.copy_subtree(tree_t::subtree_point_t(p1_child.start), p1_child.end, p1_subtree);
c1_temp.insert_subtree(tree_t::subtree_point_t(c1_temp.size()), p1_subtree);
found_match = true;
break;
}
}
if (!found_match)
{
// If no matching child, we need to generate a new subtree of the correct type
auto& tree = tree_t::get_thread_local(program);
tree.clear(program);
config.generator.get().generate(tree, {program, needed_type.id, config.replacement_min_depth, config.replacement_max_depth});
c1_temp.insert_subtree(tree_t::subtree_point_t(c1_temp.size()), tree);
}
}
// Do the same for the other direction (c2)
for (size_t i = 0; i < p1_info.argument_types.size(); i++)
{
const auto& needed_type = p1_info.argument_types[i];
bool found_match = false;
// Try to find a matching child from p2
for (size_t j = 0; j < p2_info.argument_types.size(); j++)
{
if (needed_type.id == p2_info.argument_types[j].id)
{
// Copy this child subtree from p2
auto& p2_child = children_data_p2[j];
tree_t p2_subtree(program);
p2.copy_subtree(tree_t::subtree_point_t(p2_child.start), p2_child.end, p2_subtree);
c2_temp.insert_subtree(tree_t::subtree_point_t(c2_temp.size()), p2_subtree);
found_match = true;
break;
}
}
if (!found_match)
{
// If no matching child, we need to generate a new subtree of the correct type
auto& tree = tree_t::get_thread_local(program);
tree.clear(program);
config.generator.get().generate(tree, {program, needed_type.id, config.replacement_min_depth, config.replacement_max_depth});
c2_temp.insert_subtree(tree_t::subtree_point_t(c2_temp.size()), tree);
}
}
// Replace the original subtrees with our new ones
c1.replace_subtree(point1, c1_temp);
c2.replace_subtree(point2, c2_temp);
}
#if BLT_DEBUG_LEVEL >= 2
if (!c1.check(detail::debug::context_ptr) || !c2.check(detail::debug::context_ptr))
throw std::runtime_error("Tree check failed");
#endif
return true;
}

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

48
README.md Normal file
View File

@ -0,0 +1,48 @@
# blt-gp
Genetic Programming (GP) library for C++. Integrates directly into the C++ type system, a safe replacement for lilgp without performance compromises.
## Easy to use and safe, without compromise
Using blt-gp is very easy, import the program header, define your operators, and then call the relevant functions to set up the program to your liking.
Concrete examples can be found in the example folder and compiled using the CMake argument `-DBUILD_EXAMPLES=ON`
Operator example:
```c++
// Constructor takes an optional string that describes the function of the operator. Used in printing.
blt::gp::operation_t add([](float a, float b) {
return a + b;
}, "add");
```
Operators can return different types or have differing types as arguments, this will automatically be recognized by the library.
```c++
// Any callable type can be passed as the function parameter
// so long as the function exists for the lifetime of the gp_program object.
bool compare_impl(bool type, float a, float b)
{
return type ? (a > b) : (a < b);
}
blt::gp::operation_t compare(compare_impl, "compare");
```
Please note that if a type doesn't have a way to produce a terminal, or doesn't have a way of converting from a type that has a terminal, you will get an error.
Defining your fitness function is just as easy:
```c++
const auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t current_index) {
// Evaluate your fitness
// ...
// Write to the fitness out parameter. Only "adjusted_fitness" is used during evaluation.
fitness.raw_fitness = static_cast<double>(fitness.hits);
fitness.standardized_fitness = fitness.raw_fitness;
// Higher values = better. Should be bounded [0, 1]
fitness.adjusted_fitness = 1.0 - (1.0 / (1.0 + fitness.standardized_fitness));
// returning true from this function ends the evaluation of the program, as this signals that a valid solution was found.
// This function is allowed to return void.
return static_cast<blt::size_t>(fitness.hits) == training_cases.size();
};
```
## Performance
## Bibliography
Rice (Cammeo and Osmancik) [Dataset]. (2019). UCI Machine Learning Repository. https://doi.org/10.24432/C5MW4Z.

3826
Rice_Cammeo_Osmancik.arff Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/usr/bin/python3
#!python3
import subprocess

File diff suppressed because it is too large Load Diff

44
default.nix Normal file
View File

@ -0,0 +1,44 @@
{ pkgs ? (import <nixpkgs> {
config.allowUnfree = true;
config.segger-jlink.acceptLicense = true;
}), ... }:
pkgs.mkShell
{
buildInputs = with pkgs; [
cmake
gcc
clang
emscripten
ninja
renderdoc
valgrind
gtest
opentelemetry-cpp
opentelemetry-cpp.dev
];
nativeBuildInputs = with pkgs; [
pkg-config
opentelemetry-cpp
opentelemetry-cpp.dev
];
propagatedBuildInputs = with pkgs; [
abseil-cpp
protobuf
grpc
prometheus-cpp
prometheus-cpp.dev
openssl
openssl.dev
opentelemetry-cpp
opentelemetry-cpp.dev
civetweb
civetweb.dev
c-ares
c-ares.dev
nlohmann_json
glibc
glibc.dev
curl
];
LD_LIBRARY_PATH="/run/opengl-driver/lib:/run/opengl-driver-32/lib";
}

View File

@ -1,41 +0,0 @@
Performance counter stats for './cmake-build-release/blt-symbolic-regression-example' (30 runs):
24,277,728,279 branches ( +- 19.01% ) (20.47%)
76,457,616 branch-misses # 0.31% of all branches ( +- 17.97% ) (21.41%)
14,213,192 cache-misses # 4.73% of all cache refs ( +- 14.24% ) (22.52%)
300,581,049 cache-references ( +- 21.08% ) (23.68%)
48,914,779,668 cycles ( +- 19.65% ) (24.80%)
123,068,193,359 instructions # 2.52 insn per cycle ( +- 19.44% ) (25.09%)
0 alignment-faults
4,202 cgroup-switches ( +- 13.56% )
115,962 faults ( +- 10.95% )
871,101,993 ns duration_time ( +- 13.40% )
11,507,605,674 ns user_time ( +- 3.56% )
299,016,204 ns system_time ( +- 3.32% )
41,446,831,795 L1-dcache-loads ( +- 19.28% ) (24.69%)
167,603,194 L1-dcache-load-misses # 0.40% of all L1-dcache accesses ( +- 22.47% ) (23.95%)
81,992,073 L1-dcache-prefetches ( +- 25.34% ) (23.24%)
350,398,072 L1-icache-loads ( +- 15.30% ) (22.70%)
909,504 L1-icache-load-misses # 0.26% of all L1-icache accesses ( +- 14.46% ) (22.18%)
14,271,381 dTLB-loads ( +- 20.04% ) (21.90%)
1,559,972 dTLB-load-misses # 10.93% of all dTLB cache accesses ( +- 14.74% ) (21.39%)
246,888 iTLB-loads ( +- 21.69% ) (20.54%)
403,152 iTLB-load-misses # 163.29% of all iTLB cache accesses ( +- 13.35% ) (19.94%)
210,585,840 l2_request_g1.all_no_prefetch ( +- 20.07% ) (19.93%)
115,962 page-faults ( +- 10.95% )
115,958 page-faults:u ( +- 10.95% )
3 page-faults:k ( +- 4.54% )
41,209,739,257 L1-dcache-loads ( +- 19.02% ) (19.60%)
181,755,898 L1-dcache-load-misses # 0.44% of all L1-dcache accesses ( +- 20.60% ) (20.01%)
<not supported> LLC-loads
<not supported> LLC-load-misses
425,056,352 L1-icache-loads ( +- 12.27% ) (20.43%)
1,076,486 L1-icache-load-misses # 0.31% of all L1-icache accesses ( +- 10.84% ) (20.98%)
15,418,419 dTLB-loads ( +- 17.74% ) (21.24%)
1,648,473 dTLB-load-misses # 11.55% of all dTLB cache accesses ( +- 13.11% ) (20.94%)
325,141 iTLB-loads ( +- 26.87% ) (20.80%)
459,828 iTLB-load-misses # 186.25% of all iTLB cache accesses ( +- 11.50% ) (20.34%)
94,270,593 L1-dcache-prefetches ( +- 22.82% ) (20.09%)
<not supported> L1-dcache-prefetch-misses
0.871 +- 0.117 seconds time elapsed ( +- 13.40% )

File diff suppressed because it is too large Load Diff

64
examples/examples_base.h Normal file
View File

@ -0,0 +1,64 @@
#pragma once
/*
* 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 BLT_GP_EXAMPLESEXAMPLES_BASE_H
#define BLT_GP_EXAMPLESEXAMPLES_BASE_H
#include <blt/gp/program.h>
namespace blt::gp::example
{
class example_base_t
{
public:
template<typename SEED>
example_base_t(SEED&& seed, const prog_config_t& config): program{std::forward<SEED>(seed), config}
{
}
example_base_t& set_crossover_selection(selection_t& sel)
{
crossover_sel = &sel;
return *this;
}
example_base_t& set_mutation_selection(selection_t& sel)
{
mutation_sel = &sel;
return *this;
}
example_base_t& set_reproduction_selection(selection_t& sel)
{
reproduction_sel = &sel;
return *this;
}
gp_program& get_program() { return program; }
const gp_program& get_program() const { return program; }
protected:
gp_program program;
selection_t* crossover_sel = nullptr;
selection_t* mutation_sel = nullptr;
selection_t* reproduction_sel = nullptr;
std::function<bool(const tree_t&, fitness_t&, size_t)> fitness_function_ref;
};
}
#endif //BLT_GP_EXAMPLESEXAMPLES_BASE_H

View File

@ -1,33 +0,0 @@
#pragma once
/*
* 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 BLT_GP_OPERATIONS_COMMON_H
#define BLT_GP_OPERATIONS_COMMON_H
#include <blt/gp/program.h>
blt::gp::operation_t add([](float a, float b) { return a + b; }, "add");
blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub");
blt::gp::operation_t mul([](float a, float b) { return a * b; }, "mul");
blt::gp::operation_t pro_div([](float a, float b) { return b == 0.0f ? 1.0f : a / b; }, "div");
blt::gp::operation_t op_sin([](float a) { return std::sin(a); }, "sin");
blt::gp::operation_t op_cos([](float a) { return std::cos(a); }, "cos");
blt::gp::operation_t op_exp([](float a) { return std::exp(a); }, "exp");
blt::gp::operation_t op_log([](float a) { return a == 0.0f ? 0.0f : std::log(a); }, "log");
#endif //BLT_GP_OPERATIONS_COMMON_H

View File

@ -1,353 +0,0 @@
/*
* This rice classification example uses data from the UC Irvine Machine Learning repository.
* The data for this example can be found at:
* https://archive.ics.uci.edu/dataset/545/rice+cammeo+and+osmancik
*
* 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 <blt/gp/program.h>
#include <blt/profiling/profiler_v2.h>
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
#include <blt/std/format.h>
#include <blt/parse/argparse.h>
#include <iostream>
#include "operations_common.h"
#include "blt/fs/loader.h"
static const auto SEED_FUNC = [] { return std::random_device()(); };
enum class rice_type_t
{
Cammeo,
Osmancik
};
struct rice_record
{
float area;
float perimeter;
float major_axis_length;
float minor_axis_length;
float eccentricity;
float convex_area;
float extent;
rice_type_t type;
};
std::vector<rice_record> training_cases;
std::vector<rice_record> testing_cases;
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(2)
.set_crossover_chance(0.9)
.set_mutation_chance(0.1)
.set_reproduction_chance(0)
.set_max_generations(50)
.set_pop_size(5000)
.set_thread_count(0);
blt::gp::gp_program program{SEED_FUNC, config};
auto lit = blt::gp::operation_t([]() {
return program.get_random().get_float(-32000.0f, 32000.0f);
}, "lit").set_ephemeral();
blt::gp::operation_t op_area([](const rice_record& rice_data) {
return rice_data.area;
}, "area");
blt::gp::operation_t op_perimeter([](const rice_record& rice_data) {
return rice_data.perimeter;
}, "perimeter");
blt::gp::operation_t op_major_axis_length([](const rice_record& rice_data) {
return rice_data.major_axis_length;
}, "major_axis_length");
blt::gp::operation_t op_minor_axis_length([](const rice_record& rice_data) {
return rice_data.minor_axis_length;
}, "minor_axis_length");
blt::gp::operation_t op_eccentricity([](const rice_record& rice_data) {
return rice_data.eccentricity;
}, "eccentricity");
blt::gp::operation_t op_convex_area([](const rice_record& rice_data) {
return rice_data.convex_area;
}, "convex_area");
blt::gp::operation_t op_extent([](const rice_record& rice_data) {
return rice_data.extent;
}, "extent");
constexpr auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t) {
for (auto& training_case : training_cases)
{
auto v = current_tree.get_evaluation_value<float>(&training_case);
switch (training_case.type)
{
case rice_type_t::Cammeo:
if (v >= 0)
fitness.hits++;
break;
case rice_type_t::Osmancik:
if (v < 0)
fitness.hits++;
break;
}
}
fitness.raw_fitness = static_cast<double>(fitness.hits);
fitness.standardized_fitness = fitness.raw_fitness;
fitness.adjusted_fitness = 1.0 - (1.0 / (1.0 + fitness.standardized_fitness));
return static_cast<blt::size_t>(fitness.hits) == training_cases.size();
};
void load_rice_data(std::string_view rice_file_path)
{
auto rice_file_data = blt::fs::getLinesFromFile(rice_file_path);
size_t index = 0;
while (!blt::string::contains(rice_file_data[index++], "@DATA"))
{}
std::vector<rice_record> c;
std::vector<rice_record> o;
for (std::string_view v : blt::itr_offset(rice_file_data, index))
{
auto data = blt::string::split(v, ',');
rice_record r{std::stof(data[0]), std::stof(data[1]), std::stof(data[2]), std::stof(data[3]), std::stof(data[4]), std::stof(data[5]),
std::stof(data[6]), blt::string::contains(data[7], "Cammeo") ? rice_type_t::Cammeo : rice_type_t::Osmancik};
switch (r.type)
{
case rice_type_t::Cammeo:
c.push_back(r);
break;
case rice_type_t::Osmancik:
o.push_back(r);
break;
}
}
blt::size_t total_records = c.size() + o.size();
blt::size_t training_size = std::min(total_records / 3, 1000ul);
for (blt::size_t i = 0; i < training_size; i++)
{
auto& random = program.get_random();
auto& vec = random.choice() ? c : o;
auto pos = random.get_i64(0, static_cast<blt::i64>(vec.size()));
training_cases.push_back(vec[pos]);
vec.erase(vec.begin() + pos);
}
testing_cases.insert(testing_cases.end(), c.begin(), c.end());
testing_cases.insert(testing_cases.end(), o.begin(), o.end());
std::shuffle(testing_cases.begin(), testing_cases.end(), program.get_random());
BLT_INFO("Created training set of size %ld, testing set is of size %ld", training_size, testing_cases.size());
}
struct test_results_t
{
blt::size_t cc = 0;
blt::size_t co = 0;
blt::size_t oo = 0;
blt::size_t oc = 0;
blt::size_t hits = 0;
blt::size_t size = 0;
double percent_hit = 0;
test_results_t& operator+=(const test_results_t& a)
{
cc += a.cc;
co += a.co;
oo += a.oo;
oc += a.oc;
hits += a.hits;
size += a.size;
percent_hit += a.percent_hit;
return *this;
}
test_results_t& operator/=(blt::size_t s)
{
cc /= s;
co /= s;
oo /= s;
oc /= s;
hits /= s;
size /= s;
percent_hit /= static_cast<double>(s);
return *this;
}
friend bool operator<(const test_results_t& a, const test_results_t& b)
{
return a.hits < b.hits;
}
friend bool operator>(const test_results_t& a, const test_results_t& b)
{
return a.hits > b.hits;
}
};
test_results_t test_individual(blt::gp::individual_t& i)
{
test_results_t results;
for (auto& testing_case : testing_cases)
{
auto result = i.tree.get_evaluation_value<float>(&testing_case);
switch (testing_case.type)
{
case rice_type_t::Cammeo:
if (result >= 0)
results.cc++; // cammeo cammeo
else
results.co++; // cammeo osmancik
break;
case rice_type_t::Osmancik:
if (result < 0)
results.oo++; // osmancik osmancik
else
results.oc++; // osmancik cammeo
break;
}
}
results.hits = results.cc + results.oo;
results.size = testing_cases.size();
results.percent_hit = static_cast<double>(results.hits) / static_cast<double>(results.size) * 100;
return results;
}
int main(int argc, const char** argv)
{
blt::arg_parse parser;
parser.addArgument(blt::arg_builder{"-f", "--file"}.setHelp("File for rice data. Should be in .arff format.").setRequired().build());
auto args = parser.parse_args(argc, argv);
if (!args.contains("file"))
{
BLT_WARN("Please provide path to file with -f or --file");
return 1;
}
auto rice_file_path = args.get<std::string>("file");
BLT_INFO("Starting BLT-GP Rice Classification Example");
BLT_START_INTERVAL("Rice Classification", "Main");
BLT_DEBUG("Setup Fitness cases");
load_rice_data(rice_file_path);
BLT_DEBUG("Setup Types and Operators");
blt::gp::operator_builder<rice_record> builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_exp, op_log, lit, op_area, op_perimeter, op_major_axis_length,
op_minor_axis_length, op_eccentricity, op_convex_area, op_extent));
BLT_DEBUG("Generate Initial Population");
auto sel = blt::gp::select_tournament_t{};
program.generate_population(program.get_typesystem().get_type<float>().id(), fitness_function, sel, sel, sel);
BLT_DEBUG("Begin Generation Loop");
while (!program.should_terminate())
{
BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
BLT_START_INTERVAL("Rice Classification", "Gen");
program.create_next_generation();
BLT_END_INTERVAL("Rice Classification", "Gen");
BLT_TRACE("Move to next generation");
BLT_START_INTERVAL("Rice Classification", "Fitness");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
BLT_END_INTERVAL("Rice Classification", "Fitness");
auto& stats = program.get_population_stats();
BLT_TRACE("Stats:");
BLT_TRACE("Average fitness: %lf", stats.average_fitness.load());
BLT_TRACE("Best fitness: %lf", stats.best_fitness.load());
BLT_TRACE("Worst fitness: %lf", stats.worst_fitness.load());
BLT_TRACE("Overall fitness: %lf", stats.overall_fitness.load());
BLT_TRACE("----------------------------------------------");
std::cout << std::endl;
}
BLT_END_INTERVAL("Rice Classification", "Main");
std::vector<std::pair<test_results_t, blt::gp::individual_t*>> results;
for (auto& i : program.get_current_pop().get_individuals())
results.emplace_back(test_individual(i), &i);
std::sort(results.begin(), results.end(), [](const auto& a, const auto& b) {
return a.first > b.first;
});
BLT_INFO("Best results:");
for (blt::size_t index = 0; index < 3; index++)
{
const auto& record = results[index].first;
const auto& i = *results[index].second;
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", record.hits, record.size, record.percent_hit);
BLT_DEBUG("Cammeo Cammeo: %ld", record.cc);
BLT_DEBUG("Cammeo Osmancik: %ld", record.co);
BLT_DEBUG("Osmancik Osmancik: %ld", record.oo);
BLT_DEBUG("Osmancik Cammeo: %ld", record.oc);
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(program, std::cout);
std::cout << "\n";
}
BLT_INFO("Worst Results:");
for (blt::size_t index = 0; index < 3; index++)
{
const auto& record = results[results.size() - 1 - index].first;
const auto& i = *results[results.size() - 1 - index].second;
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", record.hits, record.size, record.percent_hit);
BLT_DEBUG("Cammeo Cammeo: %ld", record.cc);
BLT_DEBUG("Cammeo Osmancik: %ld", record.co);
BLT_DEBUG("Osmancik Osmancik: %ld", record.oo);
BLT_DEBUG("Osmancik Cammeo: %ld", record.oc);
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
std::cout << "\n";
}
BLT_INFO("Average Results");
test_results_t avg{};
for (const auto& v : results)
avg += v.first;
avg /= results.size();
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", avg.hits, avg.size, avg.percent_hit);
BLT_DEBUG("Cammeo Cammeo: %ld", avg.cc);
BLT_DEBUG("Cammeo Osmancik: %ld", avg.co);
BLT_DEBUG("Osmancik Osmancik: %ld", avg.oo);
BLT_DEBUG("Osmancik Cammeo: %ld", avg.oc);
std::cout << "\n";
BLT_PRINT_PROFILE("Rice Classification", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL);
#ifdef BLT_TRACK_ALLOCATIONS
BLT_TRACE("Total Allocations: %ld times with a total of %s", blt::gp::tracker.getAllocations(),
blt::byte_convert_t(blt::gp::tracker.getAllocatedBytes()).convert_to_nearest_type().to_pretty_string().c_str());
#endif
return 0;
}

View File

@ -0,0 +1,180 @@
#pragma once
/*
* 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 BLT_GP_EXAMPLES_RICE_CLASSIFICATION_H
#define BLT_GP_EXAMPLES_RICE_CLASSIFICATION_H
#include "examples_base.h"
namespace blt::gp::example
{
class rice_classification_t : public example_base_t
{
private:
enum class rice_type_t
{
Cammeo,
Osmancik
};
struct rice_record
{
float area;
float perimeter;
float major_axis_length;
float minor_axis_length;
float eccentricity;
float convex_area;
float extent;
rice_type_t type;
};
bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const;
public:
template <typename SEED>
rice_classification_t(SEED&& seed, const prog_config_t& config): example_base_t{std::forward<SEED>(seed), config}
{
BLT_INFO("Starting BLT-GP Rice Classification Example");
fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i)
{
return fitness_function(t, f, i);
};
}
void make_operators();
void load_rice_data(std::string_view rice_file_path);
[[nodiscard]] confusion_matrix_t test_individual(const individual_t& individual) const;
void execute(const std::string_view rice_file_path)
{
load_rice_data(rice_file_path);
make_operators();
generate_initial_population();
run_generation_loop();
evaluate_individuals();
print_best();
print_average();
}
void run_generation_loop()
{
BLT_DEBUG("Begin Generation Loop");
while (!program.should_terminate())
{
BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
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_TRACE("----------------------------------------------");
std::cout << std::endl;
}
}
void evaluate_individuals()
{
results.clear();
for (auto& i : program.get_current_pop().get_individuals())
results.emplace_back(test_individual(i), &i);
std::sort(results.begin(), results.end(), [](const auto& a, const auto& b)
{
return a.first > b.first;
});
}
void generate_initial_population()
{
BLT_DEBUG("Generate Initial Population");
static auto sel = select_tournament_t{};
if (crossover_sel == nullptr)
crossover_sel = &sel;
if (mutation_sel == nullptr)
mutation_sel = &sel;
if (reproduction_sel == nullptr)
reproduction_sel = &sel;
program.generate_initial_population(program.get_typesystem().get_type<float>().id());
program.setup_generational_evaluation(fitness_function_ref, *crossover_sel, *mutation_sel, *reproduction_sel);
}
void print_best(const size_t amount = 3)
{
BLT_INFO("Best results:");
for (size_t index = 0; index < amount; index++)
{
const auto& record = results[index].first;
const auto& i = *results[index].second;
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", record.get_hits(), record.get_total(), record.get_percent_hit());
std::cout << record.pretty_print() << std::endl;
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(std::cout);
std::cout << "\n";
}
}
void print_worst(const size_t amount = 3) const
{
BLT_INFO("Worst Results:");
for (size_t index = 0; index < amount; index++)
{
const auto& record = results[results.size() - 1 - index].first;
const auto& i = *results[results.size() - 1 - index].second;
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", record.get_hits(), record.get_total(), record.get_percent_hit());
std::cout << record.pretty_print() << std::endl;
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
std::cout << "\n";
}
}
void print_average()
{
BLT_INFO("Average Results");
confusion_matrix_t avg{};
avg.set_name_a("cammeo");
avg.set_name_b("osmancik");
for (const auto& [matrix, _] : results)
avg += matrix;
avg /= results.size();
BLT_INFO("Hits %ld, Total Cases %ld, Percent Hit: %lf", avg.get_hits(), avg.get_total(), avg.get_percent_hit());
std::cout << avg.pretty_print() << std::endl;
std::cout << "\n";
}
auto& get_results() { return results; }
const auto& get_results() const { return results; }
private:
std::vector<rice_record> training_cases;
std::vector<rice_record> testing_cases;
std::vector<std::pair<confusion_matrix_t, individual_t*>> results;
};
}
#endif //BLT_GP_EXAMPLES_RICE_CLASSIFICATION_H

View File

@ -0,0 +1,224 @@
/*
* This rice classification example uses data from the UC Irvine Machine Learning repository.
* The data for this example can be found at:
* https://archive.ics.uci.edu/dataset/545/rice+cammeo+and+osmancik
*
* 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 <blt/gp/program.h>
#include <blt/profiling/profiler_v2.h>
#include <blt/gp/tree.h>
#include <blt/logging/logging.h>
#include <blt/format/format.h>
#include <blt/parse/argparse.h>
#include <iostream>
#include <filesystem>
#include "../rice_classification.h"
#include "blt/fs/loader.h"
static const auto SEED_FUNC = [] { return std::random_device()(); };
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(2)
.set_crossover_chance(0.8)
.set_mutation_chance(0.1)
.set_reproduction_chance(0)
.set_max_generations(50)
.set_pop_size(500)
.set_thread_count(0);
int main(int argc, const char** argv)
{
blt::arg_parse parser;
parser.addArgument(blt::arg_builder{"file"}
.setHelp("File for rice data. Should be in .arff format.").setDefault("../datasets/Rice_Cammeo_Osmancik.arff").build());
auto args = parser.parse_args(argc, argv);
if (!args.contains("file"))
{
BLT_WARN("Please provide path to file with -f or --file");
return 1;
}
auto rice_file_path = args.get<std::string>("file");
blt::gp::example::rice_classification_t rice_classification{SEED_FUNC, config};
rice_classification.execute(rice_file_path);
return 0;
}
void blt::gp::example::rice_classification_t::make_operators()
{
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_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(-32000.0f, 32000.0f);
}, "lit").set_ephemeral();
static operation_t op_area([](const rice_record& rice_data)
{
return rice_data.area;
}, "area");
static operation_t op_perimeter([](const rice_record& rice_data)
{
return rice_data.perimeter;
}, "perimeter");
static operation_t op_major_axis_length([](const rice_record& rice_data)
{
return rice_data.major_axis_length;
}, "major_axis_length");
static operation_t op_minor_axis_length([](const rice_record& rice_data)
{
return rice_data.minor_axis_length;
}, "minor_axis_length");
static operation_t op_eccentricity([](const rice_record& rice_data)
{
return rice_data.eccentricity;
}, "eccentricity");
static operation_t op_convex_area([](const rice_record& rice_data)
{
return rice_data.convex_area;
}, "convex_area");
static operation_t op_extent([](const rice_record& rice_data)
{
return rice_data.extent;
}, "extent");
operator_builder<rice_record> builder{};
builder.build(add, sub, mul, pro_div, op_exp, op_log, lit, op_area, op_perimeter, op_major_axis_length,
op_minor_axis_length, op_eccentricity, op_convex_area, op_extent);
program.set_operations(builder.grab());
}
bool blt::gp::example::rice_classification_t::fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const
{
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)
{
case rice_type_t::Cammeo:
if (v >= 0)
fitness.hits++;
break;
case rice_type_t::Osmancik:
if (v < 0)
fitness.hits++;
break;
}
}
fitness.raw_fitness = static_cast<double>(fitness.hits);
fitness.standardized_fitness = fitness.raw_fitness;
// fitness.adjusted_fitness = 1.0 - (1.0 / (1.0 + fitness.standardized_fitness));
fitness.adjusted_fitness = fitness.standardized_fitness / static_cast<double>(training_cases.size());
return static_cast<size_t>(fitness.hits) == training_cases.size();
}
void blt::gp::example::rice_classification_t::load_rice_data(const std::string_view rice_file_path)
{
if (!std::filesystem::exists(rice_file_path))
{
BLT_WARN("Rice file not found!");
std::exit(0);
}
BLT_DEBUG("Setup Fitness cases");
auto rice_file_data = fs::getLinesFromFile(rice_file_path);
size_t index = 0;
while (!string::contains(rice_file_data[index++], "@DATA"))
{
}
std::vector<rice_record> c;
std::vector<rice_record> o;
for (const std::string_view v : iterate(rice_file_data).skip(index))
{
auto data = string::split(v, ',');
rice_record r{
std::stof(data[0]), std::stof(data[1]), std::stof(data[2]), std::stof(data[3]), std::stof(data[4]), std::stof(data[5]),
std::stof(data[6]), string::contains(data[7], "Cammeo") ? rice_type_t::Cammeo : rice_type_t::Osmancik
};
switch (r.type)
{
case rice_type_t::Cammeo:
c.push_back(r);
break;
case rice_type_t::Osmancik:
o.push_back(r);
break;
}
}
const size_t total_records = c.size() + o.size();
const size_t testing_size = total_records / 3;
for (size_t i = 0; i < testing_size; i++)
{
auto& random = program.get_random();
auto& vec = random.choice() ? c : o;
const auto pos = random.get_i64(0, static_cast<i64>(vec.size()));
testing_cases.push_back(vec[pos]);
vec.erase(vec.begin() + pos);
}
training_cases.insert(training_cases.end(), c.begin(), c.end());
training_cases.insert(training_cases.end(), o.begin(), o.end());
std::shuffle(training_cases.begin(), training_cases.end(), program.get_random());
BLT_INFO("Created testing set of size {}, training set is of size {}", testing_cases.size(), training_cases.size());
}
blt::gp::confusion_matrix_t blt::gp::example::rice_classification_t::test_individual(const individual_t& individual) const
{
confusion_matrix_t confusion_matrix;
confusion_matrix.set_name_a("cammeo");
confusion_matrix.set_name_b("osmancik");
for (auto& testing_case : testing_cases)
{
const auto result = individual.tree.get_evaluation_value<float>(testing_case);
switch (testing_case.type)
{
case rice_type_t::Cammeo:
if (result >= 0)
confusion_matrix.is_A_predicted_A(); // cammeo cammeo
else
confusion_matrix.is_A_predicted_B(); // cammeo osmancik
break;
case rice_type_t::Osmancik:
if (result < 0)
confusion_matrix.is_B_predicted_B(); // osmancik osmancik
else
confusion_matrix.is_B_predicted_A(); // osmancik cammeo
break;
}
}
return confusion_matrix;
}

View File

@ -0,0 +1,64 @@
/*
* <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 "../symbolic_regression.h"
// you can either use a straight numeric seed, or provide a function which produces a u64 output which will initialize the thread local random number generators.
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::grow_generator_t grow_generator;
blt::gp::full_generator_t full_generator;
blt::gp::ramped_half_initializer_t ramped_half_initializer;
blt::gp::full_initializer_t full_initializer;
int main()
{
// config options can be chained together to form compound statements.
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(2)
.set_crossover_chance(0.9)
.set_mutation_chance(0.1)
.set_reproduction_chance(0.0)
.set_max_generations(50)
.set_pop_size(500)
.set_thread_count(16);
// example on how you can change the mutation config
blt::gp::mutation_t::config_t mut_config{};
mut_config.generator = grow_generator;
mut_config.replacement_min_depth = 2;
mut_config.replacement_max_depth = 6;
blt::gp::advanced_mutation_t mut_adv{mut_config};
blt::gp::mutation_t mut{mut_config};
// you can choose to set any type of system used by the GP. Mutation, Crossover, and Initializers
// (config options changed do not affect others, so you can programmatically change them at runtime)
config.set_initializer(ramped_half_initializer);
config.set_mutation(mut_adv);
// 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};
regression.execute();
return 0;
}

View File

@ -1,194 +0,0 @@
/*
* <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 <blt/gp/program.h>
#include <blt/profiling/profiler_v2.h>
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
#include <blt/std/format.h>
#include <iostream>
#include "operations_common.h"
#include "blt/math/averages.h"
//static constexpr long SEED = 41912;
static const unsigned long SEED = std::random_device()();
struct context
{
float x, y;
};
std::array<context, 200> training_cases;
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(200)
.set_crossover_chance(0.9)
.set_mutation_chance(0.1)
.set_reproduction_chance(0)
.set_max_generations(50)
.set_pop_size(20000)
.set_thread_count(0);
//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(2)
// .set_crossover_chance(0.9)
// .set_mutation_chance(0.1)
// .set_reproduction_chance(0)
// .set_max_generations(50)
// .set_pop_size(500)
// .set_thread_count(0);
blt::gp::gp_program program{SEED, config};
auto lit = blt::gp::operation_t([]() {
return program.get_random().get_float(-1.0f, 1.0f);
}, "lit").set_ephemeral();
blt::gp::operation_t op_x([](const context& context) {
return context.x;
}, "x");
constexpr auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t) {
constexpr double value_cutoff = 1.e15;
for (auto& fitness_case : training_cases)
{
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<blt::size_t>(fitness.hits) == training_cases.size();
};
float example_function(float x)
{
return x * x * x * x + x * x * x + x * x + x;
}
int main()
{
BLT_INFO("Starting BLT-GP Symbolic Regression Example");
BLT_START_INTERVAL("Symbolic Regression", "Main");
BLT_DEBUG("Setup Fitness cases");
for (auto& fitness_case : training_cases)
{
constexpr float range = 10;
constexpr float half_range = range / 2.0;
auto x = program.get_random().get_float(-half_range, half_range);
auto y = example_function(x);
fitness_case = {x, y};
}
BLT_DEBUG("Setup Types and Operators");
blt::gp::operator_builder<context> builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x));
BLT_DEBUG("Generate Initial Population");
auto sel = blt::gp::select_tournament_t{};
program.generate_population(program.get_typesystem().get_type<float>().id(), fitness_function, sel, sel, sel);
BLT_DEBUG("Begin Generation Loop");
while (!program.should_terminate())
{
BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
BLT_START_INTERVAL("Symbolic Regression", "Gen");
program.create_next_generation();
BLT_END_INTERVAL("Symbolic Regression", "Gen");
BLT_TRACE("Move to next generation");
BLT_START_INTERVAL("Symbolic Regression", "Fitness");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
BLT_END_INTERVAL("Symbolic Regression", "Fitness");
BLT_TRACE("----------------------------------------------");
std::cout << std::endl;
}
BLT_END_INTERVAL("Symbolic Regression", "Main");
auto best = program.get_best_individuals<3>();
BLT_INFO("Best approximations:");
for (auto& i_ref : best)
{
auto& i = i_ref.get();
BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(program, std::cout);
std::cout << "\n";
}
auto& stats = program.get_population_stats();
BLT_INFO("Stats:");
BLT_INFO("Average fitness: %lf", stats.average_fitness.load());
BLT_INFO("Best fitness: %lf", stats.best_fitness.load());
BLT_INFO("Worst fitness: %lf", stats.worst_fitness.load());
BLT_INFO("Overall fitness: %lf", stats.overall_fitness.load());
// TODO: make stats helper
BLT_PRINT_PROFILE("Symbolic Regression", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL);
#ifdef BLT_TRACK_ALLOCATIONS
BLT_TRACE("Total Allocations: %ld times with a total of %s, peak allocated bytes %s", blt::gp::tracker.getAllocations(),
blt::byte_convert_t(blt::gp::tracker.getAllocatedBytes()).convert_to_nearest_type().to_pretty_string().c_str(),
blt::byte_convert_t(blt::gp::tracker.getPeakAllocatedBytes()).convert_to_nearest_type().to_pretty_string().c_str());
BLT_TRACE("------------------------------------------------------");
auto evaluation_calls_v = blt::gp::evaluation_calls.get_calls();
auto evaluation_allocations_v = blt::gp::evaluation_allocations.get_calls();
BLT_TRACE("Total Evaluation Calls: %ld; Peak Bytes Allocated %s", evaluation_calls_v,
blt::string::bytes_to_pretty(blt::gp::evaluation_calls.get_value()).c_str());
BLT_TRACE("Total Evaluation Allocations: %ld; Bytes %s; Average %s", evaluation_allocations_v,
blt::string::bytes_to_pretty(blt::gp::evaluation_allocations.get_value()).c_str(),
blt::string::bytes_to_pretty(blt::average(blt::gp::evaluation_allocations.get_value(), evaluation_allocations_v)).c_str());
BLT_TRACE("Percent Evaluation calls allocate? %lf%%", blt::average(evaluation_allocations_v, evaluation_calls_v) * 100);
BLT_TRACE("------------------------------------------------------");
auto crossover_calls_v = blt::gp::crossover_calls.get_calls();
auto crossover_allocations_v = blt::gp::crossover_allocations.get_calls();
auto mutation_calls_v = blt::gp::mutation_calls.get_calls();
auto mutation_allocations_v = blt::gp::mutation_allocations.get_calls();
auto reproduction_calls_v = blt::gp::reproduction_calls.get_calls();
auto reproduction_allocations_v = blt::gp::reproduction_allocations.get_calls();
BLT_TRACE("Total Crossover Calls: %ld; Peak Bytes Allocated %s", crossover_calls_v,
blt::string::bytes_to_pretty(blt::gp::crossover_calls.get_value()).c_str());
BLT_TRACE("Total Mutation Calls: %ld; Peak Bytes Allocated %s", mutation_calls_v,
blt::string::bytes_to_pretty(blt::gp::mutation_calls.get_value()).c_str());
BLT_TRACE("Total Reproduction Calls: %ld; Peak Bytes Allocated %s", reproduction_calls_v,
blt::string::bytes_to_pretty(blt::gp::reproduction_calls.get_value()).c_str());
BLT_TRACE("Total Crossover Allocations: %ld; Bytes %s; Average %s", crossover_allocations_v,
blt::string::bytes_to_pretty(blt::gp::crossover_allocations.get_value()).c_str(),
blt::string::bytes_to_pretty(blt::average(blt::gp::crossover_allocations.get_value(), crossover_allocations_v)).c_str());
BLT_TRACE("Total Mutation Allocations: %ld; Bytes %s; Average %s", mutation_allocations_v,
blt::string::bytes_to_pretty(blt::gp::mutation_allocations.get_value()).c_str(),
blt::string::bytes_to_pretty(blt::average(blt::gp::mutation_allocations.get_value(), mutation_allocations_v)).c_str());
BLT_TRACE("Total Reproduction Allocations: %ld; Bytes %s; Average %s", reproduction_allocations_v,
blt::string::bytes_to_pretty(blt::gp::reproduction_allocations.get_value()).c_str(),
blt::string::bytes_to_pretty(blt::average(blt::gp::reproduction_allocations.get_value(), reproduction_allocations_v)).c_str());
BLT_TRACE("Percent Crossover calls allocate? %lf%%", blt::average(crossover_allocations_v, crossover_calls_v) * 100);
BLT_TRACE("Percent Mutation calls allocate? %lf%%", blt::average(mutation_allocations_v, mutation_calls_v) * 100);
BLT_TRACE("Percent Reproduction calls allocate? %lf%%", blt::average(reproduction_allocations_v, reproduction_calls_v) * 100);
#endif
return 0;
}

View File

@ -0,0 +1,227 @@
#pragma once
/*
* 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 BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H
#define BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H
#include "examples_base.h"
#include <blt/logging/logging.h>
#include <blt/format/format.h>
#include <iostream>
namespace blt::gp::example
{
class symbolic_regression_t : public example_base_t
{
public:
struct context
{
float x, y;
};
private:
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)
{
BLT_GP_UPDATE_CONTEXT(fitness_case);
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)
{
return x * x * x * x + x * x * x + x * x + x;
}
public:
template <typename SEED>
symbolic_regression_t(SEED seed, const prog_config_t& config): example_base_t{std::forward<SEED>(seed), config}
{
BLT_INFO("Starting BLT-GP Symbolic Regression Example");
BLT_DEBUG("Setup Fitness cases");
for (auto& fitness_case : training_cases)
{
constexpr float range = 10;
constexpr float half_range = range / 2.0;
const auto x = program.get_random().get_float(-half_range, half_range);
const auto y = example_function(x);
fitness_case = {x, y};
}
fitness_function_ref = [this](const tree_t& t, fitness_t& f, const size_t i) {
return fitness_function(t, f, i);
};
}
void setup_operations()
{
BLT_DEBUG("Setup Types and Operators");
static operation_t add{
// this is the function used by the operation
[](const float a, const float b) {
return a + b;
},
// this name is optional and is used if you print an individual
"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()
{
BLT_DEBUG("Generate Initial Population");
static auto sel = select_tournament_t{};
if (crossover_sel == nullptr)
crossover_sel = &sel;
if (mutation_sel == nullptr)
mutation_sel = &sel;
if (reproduction_sel == nullptr)
reproduction_sel = &sel;
program.generate_initial_population(program.get_typesystem().get_type<float>().id());
program.setup_generational_evaluation(fitness_function_ref, *crossover_sel,
*mutation_sel, *reproduction_sel);
}
void run_generation_loop()
{
BLT_DEBUG("Begin Generation Loop");
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 {}}------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
const auto& stats = program.get_population_stats();
BLT_TRACE("Avg Fit: {:0.6f}, Best Fit: {:0.6f}, Worst Fit: {:0.6f}, Overall Fit: {:0.6f}", 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));
#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: {}, Mutation {}, Reproduction {}; {}", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total);
BLT_TRACE("Value Crossover: {}, Mutation {}, Reproduction {}; {}", 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;
}
}
auto get_and_print_best()
{
const auto best = program.get_best_individuals<3>();
BLT_INFO("Best approximations:");
for (auto& i_ref : best)
{
auto& i = i_ref.get();
BLT_DEBUG("Fitness: {:0.6f}, stand: {:0.6f}, raw: {:0.6f}", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness);
i.tree.print(std::cout);
std::cout << "\n";
}
return best;
}
void print_stats() const
{
// TODO: make stats helper
const auto& stats = program.get_population_stats();
BLT_INFO("Stats:");
BLT_INFO("Average fitness: {:0.6f}", stats.average_fitness.load());
BLT_INFO("Best fitness: {:0.6f}", stats.best_fitness.load());
BLT_INFO("Worst fitness: {:0.6f}", stats.worst_fitness.load());
BLT_INFO("Overall fitness: {:0.6f}", stats.overall_fitness.load());
}
void execute()
{
setup_operations();
generate_initial_population();
run_generation_loop();
get_and_print_best();
print_stats();
}
[[nodiscard]] const auto& get_training_cases() const
{
return training_cases;
}
private:
std::array<context, 200> training_cases{};
};
}
#endif //BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H

178
include/blt/gp/allocator.h Normal file
View File

@ -0,0 +1,178 @@
#pragma once
/*
* 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 BLT_GP_ALLOCATOR_H
#define BLT_GP_ALLOCATOR_H
#include <blt/std/types.h>
#include <blt/logging/logging.h>
#include <blt/gp/util/trackers.h>
#include <cstdlib>
#include <vector>
namespace blt::gp
{
namespace detail
{
#ifndef BLT_GP_MAX_ALIGNMENT
#define BLT_GP_MAX_ALIGNMENT 8
#endif
static constexpr inline size_t MAX_ALIGNMENT = BLT_GP_MAX_ALIGNMENT;
#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;
// population gen specifics
inline call_tracker_t crossover_calls;
inline call_tracker_t mutation_calls;
inline call_tracker_t reproduction_calls;
inline call_tracker_t crossover_allocations;
inline call_tracker_t mutation_allocations;
inline call_tracker_t reproduction_allocations;
// for evaluating fitness
inline call_tracker_t evaluation_calls;
inline call_tracker_t evaluation_allocations;
#endif
class aligned_allocator
{
public:
void* allocate(blt::size_t bytes) // NOLINT
{
#ifdef BLT_TRACK_ALLOCATIONSS
tracker.allocate(bytes);
// std::cout << "Hey our aligned allocator allocated " << bytes << " bytes!\n";
#endif
#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
{
if (ptr == nullptr)
return;
#ifdef BLT_TRACK_ALLOCATIONS
tracker.deallocate(bytes);
// std::cout << "[Hey our aligned allocator deallocated " << bytes << " bytes!]\n";
#else
(void)bytes;
#endif
std::free(ptr);
}
};
template <typename T>
class tracked_allocator_t
{
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using void_pointer = void*;
using const_void_pointer = const void*;
using difference_type = blt::ptrdiff_t;
using size_type = blt::size_t;
template <class U>
struct rebind
{
typedef tracked_allocator_t<U> other;
};
pointer allocate(size_type n)
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.allocate(n * sizeof(T));
// std::cout << "Hey our tracked allocator allocated " << (n * sizeof(T)) << " bytes!\n";
#endif
return static_cast<pointer>(std::malloc(n * sizeof(T)));
}
pointer allocate(size_type n, const_void_pointer)
{
return allocate(n);
}
void deallocate(pointer p, size_type n)
{
#ifdef BLT_TRACK_ALLOCATIONS
::blt::gp::tracker.deallocate(n * sizeof(T));
// std::cout << "[Hey our tracked allocator deallocated " << (n * sizeof(T)) << " bytes!]\n";
#else
(void)n;
#endif
std::free(p);
}
template <class U, class... Args>
void construct(U* p, Args&&... args)
{
new(p) T(std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p)
{
p->~T();
}
[[nodiscard]] size_type max_size() const noexcept
{
return std::numeric_limits<size_type>::max();
}
};
template <class T1, class T2>
inline static bool operator==(const tracked_allocator_t<T1>& lhs, const tracked_allocator_t<T2>& rhs) noexcept
{
return &lhs == &rhs;
}
template <class T1, class T2>
inline static bool operator!=(const tracked_allocator_t<T1>& lhs, const tracked_allocator_t<T2>& rhs) noexcept
{
return &lhs != &rhs;
}
#ifdef BLT_TRACK_ALLOCATIONS
template<typename T>
using tracked_vector = std::vector<T, tracked_allocator_t<T>>;
#else
template <typename T>
using tracked_vector = std::vector<T>;
#endif
}
#endif //BLT_GP_ALLOCATOR_H

View File

@ -22,6 +22,7 @@
#include <utility>
#include <thread>
#include <blt/std/types.h>
#include <blt/meta/config_generator.h>
#include <blt/gp/generators.h>
#include <blt/gp/transformers.h>
@ -29,10 +30,11 @@ namespace blt::gp
{
struct prog_config_t
{
blt::size_t population_size = 500;
blt::size_t max_generations = 50;
blt::size_t initial_min_tree_size = 3;
blt::size_t initial_max_tree_size = 10;
size_t population_size = 500;
size_t max_generations = 50;
size_t initial_min_tree_size = 2;
size_t initial_max_tree_size = 6;
size_t max_tree_depth = 17;
// percent chance that we will do crossover
double crossover_chance = 0.8;
@ -42,7 +44,7 @@ namespace blt::gp
double reproduction_chance = 0.1;
// everything else will just be selected
blt::size_t elites = 0;
size_t elites = 0;
bool try_mutation_on_crossover_failure = true;
@ -50,9 +52,9 @@ namespace blt::gp
std::reference_wrapper<crossover_t> crossover;
std::reference_wrapper<population_initializer_t> pop_initializer;
blt::size_t threads = std::thread::hardware_concurrency();
size_t threads = std::thread::hardware_concurrency();
// number of elements each thread should pull per execution. this is for granularity performance and can be optimized for better results!
blt::size_t evaluation_size = 4;
size_t evaluation_size = 4;
// default config (ramped half-and-half init) or for buildering
prog_config_t();
@ -146,6 +148,12 @@ namespace blt::gp
return *this;
}
prog_config_t& set_max_tree_depth(const size_t depth)
{
max_tree_depth = depth;
return *this;
}
prog_config_t& set_reproduction_chance(double chance)
{
reproduction_chance = chance;

48
include/blt/gp/defines.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
/*
* 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 BLT_GP_DEFINES_H
#define BLT_GP_DEFINES_H
#include <blt/std/defines.h>
#if BLT_DEBUG_LEVEL > 0
#if defined(__has_include) &&__has_include(<opentelemetry/version.h>)
#define BLT_DEBUG_OTEL_ENABLED 1
#endif
#endif
#if BLT_DEBUG_LEVEL > 1
#define BLT_GP_DEBUG_TRACK_ALLOCATIONS
#endif
#if BLT_DEBUG_LEVEL > 2
#define BLT_GP_DEBUG_CHECK_TREES
#endif
#ifdef BLT_GP_DEBUG_TRACK_ALLOCATIONS
#undef BLT_GP_DEBUG_TRACK_ALLOCATIONS
#define BLT_GP_DEBUG_TRACK_ALLOCATIONS
#endif
#ifdef BLT_GP_DEBUG_CHECK_TREES
#undef BLT_GP_DEBUG_CHECK_TREES
#define BLT_GP_DEBUG_CHECK_TREES 1
#endif
#endif //BLT_GP_DEFINES_H

View File

@ -20,33 +20,14 @@
#define BLT_GP_FWDECL_H
#include <functional>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <blt/std/types.h>
#include <blt/gp/stats.h>
#include <ostream>
#include <cstdlib>
#include <mutex>
#include <atomic>
#include <blt/std/mmap.h>
#include <blt/gp/util/trackers.h>
#include <blt/gp/allocator.h>
namespace blt::gp
{
#ifdef BLT_TRACK_ALLOCATIONS
inline allocation_tracker_t tracker;
// population gen specifics
inline call_tracker_t crossover_calls;
inline call_tracker_t mutation_calls;
inline call_tracker_t reproduction_calls;
inline call_tracker_t crossover_allocations;
inline call_tracker_t mutation_allocations;
inline call_tracker_t reproduction_allocations;
// for evaluating fitness
inline call_tracker_t evaluation_calls;
inline call_tracker_t evaluation_allocations;
#endif
class gp_program;
class type;
@ -55,6 +36,8 @@ namespace blt::gp
struct type_id;
struct operator_info_t;
class type_provider;
struct op_container_t;
@ -78,271 +61,6 @@ namespace blt::gp
template<typename T>
class tracked_allocator_t;
#ifdef BLT_TRACK_ALLOCATIONS
template<typename T>
using tracked_vector = std::vector<T, tracked_allocator_t<T>>;
#else
template<typename T>
using tracked_vector = std::vector<T>;
#endif
// using operation_vector_t = tracked_vector<op_container_t>;
// using individual_vector_t = tracked_vector<individual_t, tracked_allocator_t<individual_t>>;
// using tree_vector_t = tracked_vector<tree_t>;
class aligned_allocator
{
public:
void* allocate(blt::size_t bytes) // NOLINT
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.allocate(bytes);
// std::cout << "Hey our aligned allocator allocated " << bytes << " bytes!\n";
#endif
return std::aligned_alloc(8, bytes);
}
void deallocate(void* ptr, blt::size_t bytes) // NOLINT
{
if (ptr == nullptr)
return;
#ifdef BLT_TRACK_ALLOCATIONS
tracker.deallocate(bytes);
// std::cout << "[Hey our aligned allocator deallocated " << bytes << " bytes!]\n";
#else
(void) bytes;
#endif
std::free(ptr);
}
};
template<typename Alloc = blt::aligned_huge_allocator>
class variable_bump_allocator
{
public:
explicit variable_bump_allocator(blt::size_t default_block_size = BLT_2MB_SIZE): default_block_size(default_block_size)
{}
void* allocate(blt::size_t bytes)
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.allocate(bytes);
#endif
std::scoped_lock lock(mutex);
if (head == nullptr || head->remaining_bytes_in_block() < static_cast<blt::ptrdiff_t>(bytes))
{
push_block(bytes);
}
auto ptr = head->metadata.offset;
head->metadata.offset += bytes;
++head->metadata.allocated_objects;
return ptr;
}
void deallocate(void* ptr, blt::size_t bytes)
{
if (ptr == nullptr)
return;
#ifdef BLT_TRACK_ALLOCATIONS
tracker.deallocate(bytes);
#else
(void) bytes;
#endif
std::scoped_lock lock(mutex);
block_t* blk = to_block(ptr);
--blk->metadata.allocated_objects;
if (blk->metadata.allocated_objects == 0)
{
if (blk->metadata.has_deallocated)
alloc.deallocate(blk, blk->metadata.size);
else
{
if (head == blk)
head = head->metadata.next;
else
{
auto prev = head;
auto next = head->metadata.next;
while (next != blk)
{
prev = next;
next = next->metadata.next;
}
prev->metadata.next = next->metadata.next;
}
deallocated_blocks.push_back(blk);
}
}
}
~variable_bump_allocator()
{
std::scoped_lock lock(mutex);
for (auto* blk : deallocated_blocks)
{
alloc.deallocate(blk, blk->metadata.size);
}
auto cur = head;
while (cur != nullptr)
{
auto* ptr = cur;
ptr->metadata.has_deallocated = true;
cur = cur->metadata.next;
}
head = nullptr;
}
private:
struct block_t
{
struct block_metadata_t
{
blt::size_t size;
blt::size_t allocated_objects : 63;
bool has_deallocated : 1;
block_t* next;
blt::u8* offset;
} metadata;
blt::u8 buffer[8]{};
explicit block_t(blt::size_t size): metadata{size, 0, false, nullptr, nullptr}
{
reset();
}
void reset()
{
metadata.offset = buffer;
metadata.allocated_objects = 0;
metadata.next = nullptr;
}
[[nodiscard]] blt::ptrdiff_t storage_size() const noexcept
{
return static_cast<blt::ptrdiff_t>(metadata.size - sizeof(typename block_t::block_metadata_t));
}
[[nodiscard]] blt::ptrdiff_t used_bytes_in_block() const noexcept
{
return static_cast<blt::ptrdiff_t>(metadata.offset - buffer);
}
[[nodiscard]] blt::ptrdiff_t remaining_bytes_in_block() const noexcept
{
return storage_size() - used_bytes_in_block();
}
};
static inline block_t* to_block(void* p)
{
return reinterpret_cast<block_t*>(reinterpret_cast<std::uintptr_t>(p) & static_cast<std::uintptr_t>(~(BLT_2MB_SIZE - 1)));
}
void push_block(blt::size_t bytes)
{
auto blk = allocate_block(bytes);
// BLT_TRACE("Allocated block %p", blk);
blk->metadata.next = head;
head = blk;
}
inline block_t* allocate_block(blt::size_t bytes)
{
if (!deallocated_blocks.empty())
{
block_t* blk = deallocated_blocks.back();
deallocated_blocks.pop_back();
blk->reset();
return blk;
}
auto size = align_size_to(bytes + sizeof(typename block_t::block_metadata_t), default_block_size);
auto* ptr = static_cast<block_t*>(alloc.allocate(size));
new(ptr) block_t{size};
return ptr;
}
private:
block_t* head = nullptr;
std::mutex mutex;
std::vector<block_t*> deallocated_blocks;
blt::size_t default_block_size;
Alloc alloc;
};
template<typename T>
class tracked_allocator_t
{
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using void_pointer = void*;
using const_void_pointer = const void*;
using difference_type = blt::ptrdiff_t;
using size_type = blt::size_t;
template<class U>
struct rebind
{
typedef tracked_allocator_t<U> other;
};
pointer allocate(size_type n)
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.allocate(n * sizeof(T));
// std::cout << "Hey our tracked allocator allocated " << (n * sizeof(T)) << " bytes!\n";
#endif
return static_cast<pointer>(std::malloc(n * sizeof(T)));
}
pointer allocate(size_type n, const_void_pointer)
{
return allocate(n);
}
void deallocate(pointer p, size_type n)
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.deallocate(n * sizeof(T));
// std::cout << "[Hey our tracked allocator deallocated " << (n * sizeof(T)) << " bytes!]\n";
#else
(void) n;
#endif
std::free(p);
}
template<class U, class... Args>
void construct(U* p, Args&& ... args)
{
new(p) T(std::forward<Args>(args)...);
}
template<class U>
void destroy(U* p)
{
p->~T();
}
[[nodiscard]] size_type max_size() const noexcept
{
return std::numeric_limits<size_type>::max();
}
};
template<class T1, class T2>
inline static bool operator==(const tracked_allocator_t<T1>& lhs, const tracked_allocator_t<T2>& rhs) noexcept
{
return &lhs == &rhs;
}
template<class T1, class T2>
inline static bool operator!=(const tracked_allocator_t<T1>& lhs, const tracked_allocator_t<T2>& rhs) noexcept
{
return &lhs != &rhs;
}
namespace detail
{
class operator_storage_test;
@ -354,16 +72,28 @@ namespace blt::gp
enum class destroy_t
{
ARGS,
PTR,
RETURN
};
using destroy_func_t = std::function<void(destroy_t, stack_allocator&)>;
using destroy_func_t = std::function<void(destroy_t, u8*)>;
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
}
#endif //BLT_GP_FWDECL_H

View File

@ -22,76 +22,35 @@
#include <blt/std/types.h>
#include <blt/gp/typesystem.h>
#include <blt/gp/stack.h>
#include <blt/gp/util/meta.h>
#include <functional>
#include <type_traits>
#include <optional>
namespace blt::gp
{
namespace detail
{
template<typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template<typename...>
struct first_arg;
template<typename First, typename... Args>
struct first_arg<First, Args...>
{
using type = First;
};
template<>
struct first_arg<>
{
using type = void;
};
template<bool b, typename... types>
struct is_same;
template<typename... types>
struct is_same<true, types...> : public std::false_type
{
};
template<typename... types>
struct is_same<false, types...> : public std::is_same<types...>
{
};
template<typename... types>
constexpr bool is_same_v = is_same<sizeof...(types) == 0, types...>::value;
struct empty_t
{
};
}
template <typename Return, typename... Args>
struct call_with
{
template<blt::u64 index>
[[nodiscard]] inline constexpr static blt::size_t getByteOffset()
template <u64 index>
[[nodiscard]] constexpr static size_t getByteOffset()
{
blt::size_t offset = 0;
blt::size_t current_index = 0;
size_t offset = 0;
size_t current_index = 0;
((offset += (current_index++ > index ? stack_allocator::aligned_size<detail::remove_cv_ref<Args>>() : 0)), ...);
// BLT_INFO("offset %ld for argument %ld", offset, index);
(void)current_index;
return offset;
}
template<blt::u64... indices>
void print_args(std::integer_sequence<blt::u64, indices...>)
template <u64... indices>
void print_args(std::integer_sequence<u64, indices...>)
{
BLT_INFO("Arguments:");
(BLT_INFO("%ld: %s", indices, blt::type_string<Args>().c_str()), ...);
}
template<typename Func, blt::u64... indices, typename... ExtraArgs>
inline static constexpr Return exec_sequence_to_indices(Func&& func, stack_allocator& allocator, std::integer_sequence<blt::u64, indices...>,
template <typename Func, u64... indices, typename... ExtraArgs>
static constexpr Return exec_sequence_to_indices(Func&& func, stack_allocator& allocator, std::integer_sequence<u64, indices...>,
ExtraArgs&&... args)
{
//blt::size_t arg_size = (stack_allocator::aligned_size<detail::remove_cv_ref<Args>>() + ...);
@ -101,28 +60,39 @@ namespace blt::gp
allocator.from<detail::remove_cv_ref<Args>>(getByteOffset<indices>())...);
}
template<typename context = void, typename... NoCtxArgs>
void call_destructors_without_first(stack_allocator& read_allocator)
template<typename T>
static void call_drop(stack_allocator& read_allocator, const size_t offset)
{
if constexpr (sizeof...(NoCtxArgs) > 0)
if constexpr (blt::gp::detail::has_func_drop_v<detail::remove_cv_ref<T>>)
{
read_allocator.call_destructors<detail::remove_cv_ref<NoCtxArgs>...>();
auto [type, ptr] = read_allocator.access_pointer<detail::remove_cv_ref<T>>(offset);
// type is not ephemeral, so we must drop it.
if (!ptr.bit(0))
type.drop();
}
}
static void call_destructors(stack_allocator& read_allocator)
{
if constexpr (sizeof...(Args) > 0)
{
size_t offset = (stack_allocator::aligned_size<detail::remove_cv_ref<Args>>() + ...) - stack_allocator::aligned_size<
detail::remove_cv_ref<typename meta::arg_helper<Args...>::First>>();
((call_drop<Args>(read_allocator, offset), offset -= stack_allocator::aligned_size<detail::remove_cv_ref<Args>>()), ...);
(void)offset;
}
}
template <typename Func, typename... ExtraArgs>
Return operator()(bool has_context, Func&& func, stack_allocator& read_allocator, ExtraArgs&& ... args)
Return operator()(const bool, Func&& func, stack_allocator& read_allocator, ExtraArgs&&... args)
{
constexpr auto seq = std::make_integer_sequence<blt::u64, sizeof...(Args)>();
constexpr auto seq = std::make_integer_sequence<u64, sizeof...(Args)>();
#if BLT_DEBUG_LEVEL > 0
try
{
#endif
Return ret = exec_sequence_to_indices(std::forward<Func>(func), read_allocator, seq, std::forward<ExtraArgs>(args)...);
if (has_context)
call_destructors_without_first<Args...>(read_allocator);
else
read_allocator.call_destructors<detail::remove_cv_ref<Args>...>();
call_destructors(read_allocator);
read_allocator.pop_bytes((stack_allocator::aligned_size<detail::remove_cv_ref<Args>>() + ...));
return ret;
#if BLT_DEBUG_LEVEL > 0
@ -156,21 +126,23 @@ namespace blt::gp
constexpr operation_t(operation_t&& move) = default;
template <typename Functor>
constexpr explicit operation_t(const Functor& functor, std::optional<std::string_view> name = {}): func(functor), name(name)
{}
constexpr explicit operation_t(const Functor& functor, const std::optional<std::string_view> name = {}): func(functor), name(name)
{
}
[[nodiscard]] constexpr inline Return operator()(stack_allocator& read_allocator) const
[[nodiscard]] constexpr Return operator()(stack_allocator& read_allocator) const
{
if constexpr (sizeof...(Args) == 0)
{
return func();
} else
}
else
{
return call_with<Return, Args...>()(false, func, read_allocator);
}
}
[[nodiscard]] constexpr inline Return operator()(void* context, stack_allocator& read_allocator) const
[[nodiscard]] constexpr Return operator()(void* context, stack_allocator& read_allocator) const
{
// should be an impossible state
if constexpr (sizeof...(Args) == 0)
@ -181,7 +153,8 @@ namespace blt::gp
if constexpr (sizeof...(Args) == 1)
{
return func(ctx_ref);
} else
}
else
{
return call_without_first<Return, Args...>()(true, func, read_allocator, ctx_ref);
}
@ -190,12 +163,14 @@ namespace blt::gp
template <typename Context>
[[nodiscard]] detail::operator_func_t make_callable() const
{
return [this](void* context, stack_allocator& read_allocator, stack_allocator& write_allocator) {
return [this](void* context, stack_allocator& read_allocator, stack_allocator& write_allocator)
{
if constexpr (detail::is_same_v<Context, detail::remove_cv_ref<typename detail::first_arg<Args...>::type>>)
{
// first arg is context
write_allocator.push(this->operator()(context, read_allocator));
} else
}
else
{
// first arg isn't context
write_allocator.push(this->operator()(read_allocator));
@ -213,18 +188,24 @@ namespace blt::gp
return func;
}
inline auto set_ephemeral()
auto set_ephemeral()
{
is_ephemeral_ = true;
return *this;
}
inline bool is_ephemeral()
[[nodiscard]] bool is_ephemeral() const
{
return is_ephemeral_;
}
[[nodiscard]] bool return_has_ephemeral_drop() const
{
return detail::has_func_drop_v<detail::remove_cv_ref<Return>>;
}
operator_id id = -1;
private:
function_t func;
std::optional<std::string_view> name;

File diff suppressed because it is too large Load Diff

View File

@ -20,15 +20,16 @@
#define BLT_GP_SELECTION_H
#include <blt/gp/fwdecl.h>
#include <blt/gp/util/statistics.h>
#include <blt/gp/tree.h>
#include <blt/gp/config.h>
#include <blt/gp/random.h>
#include <blt/std/assert.h>
#include "blt/std/format.h"
#include "blt/format/format.h"
#include <atomic>
namespace blt::gp
{
struct selector_args
{
gp_program& program;
@ -38,20 +39,27 @@ namespace blt::gp
random_t& random;
};
constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop) {
namespace detail
{
constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop)
{
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;
thread_local tracked_vector<std::pair<std::size_t, double>> values;
values.clear();
for (blt::size_t i = 0; i < config.elites; i++)
for (size_t i = 0; i < config.elites; i++)
values.emplace_back(i, current_pop.get_individuals()[i].fitness.adjusted_fitness);
for (const auto& ind : blt::enumerate(current_pop.get_individuals()))
{
for (blt::size_t i = 0; i < config.elites; i++)
for (size_t i = 0; i < config.elites; i++)
{
if (ind.second.fitness.adjusted_fitness >= values[i].second)
{
@ -68,104 +76,14 @@ namespace blt::gp
}
}
for (blt::size_t i = 0; i < config.elites; i++)
for (size_t i = 0; i < config.elites; i++)
next_pop.get_individuals()[i].copy_fast(current_pop.get_individuals()[values[i].first].tree);
return config.elites;
}
return 0ul;
};
template<typename Crossover, typename Mutation, typename Reproduction>
constexpr inline auto default_next_pop_creator = [](
blt::gp::selector_args& args, Crossover& crossover_selection, Mutation& mutation_selection, Reproduction& reproduction_selection,
tree_t& c1, tree_t* c2) {
auto& [program, current_pop, current_stats, config, random] = args;
int sel = random.get_i32(0, 3);
switch (sel)
{
case 0:
if (c2 == nullptr)
return 0;
// everyone gets a chance once per loop.
if (random.choice(config.crossover_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// crossover
const tree_t* p1;
const tree_t* p2;
do
{
p1 = &crossover_selection.select(program, current_pop);
p2 = &crossover_selection.select(program, current_pop);
} while (!config.crossover.get().apply(program, *p1, *p2, c1, *c2));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
crossover_calls.call();
crossover_calls.set_value(std::max(crossover_calls.get_value(), state.getAllocatedByteDifference()));
if (state.getAllocatedByteDifference() != 0)
{
crossover_allocations.call(state.getAllocatedByteDifference());
}
#endif
return 2;
}
break;
case 1:
if (random.choice(config.mutation_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// mutation
const tree_t* p;
do
{
p = &mutation_selection.select(program, current_pop);
} while (!config.mutator.get().apply(program, *p, c1));
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
mutation_calls.call();
mutation_calls.set_value(std::max(mutation_calls.get_value(), state.getAllocatedByteDifference()));
if (state.getAllocationDifference() != 0)
{
mutation_allocations.call(state.getAllocatedByteDifference());
}
#endif
return 1;
}
break;
case 2:
if (config.reproduction_chance > 0 && random.choice(config.reproduction_chance))
{
#ifdef BLT_TRACK_ALLOCATIONS
auto state = tracker.start_measurement_thread_local();
#endif
// reproduction
c1 = reproduction_selection.select(program, current_pop);
#ifdef BLT_TRACK_ALLOCATIONS
tracker.stop_measurement_thread_local(state);
reproduction_calls.call();
reproduction_calls.set_value(std::max(reproduction_calls.get_value(), state.getAllocatedByteDifference()));
if (state.getAllocationDifference() != 0)
{
reproduction_allocations.call(state.getAllocatedByteDifference());
}
#endif
return 1;
}
break;
default:
#if BLT_DEBUG_LEVEL > 0
BLT_ABORT("This is not possible!");
#else
BLT_UNREACHABLE;
#endif
}
return 0;
};
class selection_t
{
@ -178,51 +96,65 @@ namespace blt::gp
*/
virtual const tree_t& select(gp_program& program, const population_t& pop) = 0;
/**
* Is run once on a single thread before selection begins. allows you to preprocess the generation for fitness metrics.
* TODO a method for parallel execution
*/
virtual void pre_process(gp_program&, population_t&)
{}
{
}
virtual ~selection_t() = default;
};
class select_best_t : public selection_t
class select_best_t final : public selection_t
{
public:
const tree_t& select(gp_program& program, const population_t& pop) final;
void pre_process(gp_program&, population_t&) override;
const tree_t& select(gp_program& program, const population_t& pop) override;
private:
std::atomic_uint64_t index = 0;
};
class select_worst_t : public selection_t
class select_worst_t final : public selection_t
{
public:
const tree_t& select(gp_program& program, const population_t& pop) final;
void pre_process(gp_program&, population_t&) override;
const tree_t& select(gp_program& program, const population_t& pop) override;
private:
std::atomic_uint64_t index = 0;
};
class select_random_t : public selection_t
class select_random_t final : public selection_t
{
public:
const tree_t& select(gp_program& program, const population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) override;
};
class select_tournament_t : public selection_t
class select_tournament_t final : public selection_t
{
public:
explicit select_tournament_t(blt::size_t selection_size = 3): selection_size(selection_size)
explicit select_tournament_t(const size_t selection_size = 3): selection_size(selection_size)
{
if (selection_size == 0)
BLT_ABORT("Unable to select with this size. Must select at least 1 individual_t!");
}
const tree_t& select(gp_program& program, const population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) override;
private:
const blt::size_t selection_size;
const size_t selection_size;
};
class select_fitness_proportionate_t : public selection_t
class select_fitness_proportionate_t final : public selection_t
{
public:
const tree_t& select(gp_program& program, const population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) override;
};
}
#endif //BLT_GP_SELECTION_H

View File

@ -21,20 +21,16 @@
#include <blt/std/types.h>
#include <blt/std/bump_allocator.h>
#include <blt/std/assert.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <blt/std/allocator.h>
#include <blt/std/ranges.h>
#include <blt/std/meta.h>
#include <blt/gp/fwdecl.h>
#include <blt/gp/stats.h>
#include <blt/meta/meta.h>
#include <blt/gp/util/meta.h>
#include <blt/gp/allocator.h>
#include <utility>
#include <stdexcept>
#include <cstdlib>
#include <memory>
#include <type_traits>
#include <cstring>
#include <iostream>
namespace blt::gp
{
@ -43,42 +39,39 @@ namespace blt::gp
BLT_META_MAKE_FUNCTION_CHECK(drop);
}
/**
* @brief This is the primary class that enables a type-erased GP system without compromising on performance.
*
* This class provides an efficient way to allocate, deallocate, and manage memory blocks
* in a stack-like structure. It supports operations like memory alignment, copying, moving,
* insertion, and removal of memory. This is particularly useful for performance-critical
* systems requiring temporary memory management without frequent heap allocation overhead.
*
* Types placed within this container cannot have an alignment greater than `BLT_GP_MAX_ALIGNMENT` bytes, doing so will result in unaligned pointer access.
* You can configure this by setting `BLT_GP_MAX_ALIGNMENT` as a compiler definition but be aware it will increase memory requirements.
* Setting `BLT_GP_MAX_ALIGNMENT` to lower than 8 is UB on x86-64 systems.
* Consequently, all types have a minimum storage size of `BLT_GP_MAX_ALIGNMENT` (8) bytes, meaning a char, float, int, etc. will take `BLT_GP_MAX_ALIGNMENT` bytes
*/
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>>;
constexpr static size_t PAGE_SIZE = 0x100;
using Allocator = aligned_allocator;
static constexpr size_t align_bytes(const size_t size) noexcept
{
return (size + (detail::MAX_ALIGNMENT - 1)) & ~(detail::MAX_ALIGNMENT - 1);
}
public:
static Allocator& get_allocator();
struct size_data_t
{
blt::size_t total_size_bytes = 0;
blt::size_t total_used_bytes = 0;
blt::size_t total_remaining_bytes = 0;
friend std::ostream& operator<<(std::ostream& stream, const size_data_t& data)
{
stream << "[";
stream << data.total_used_bytes << " / " << data.total_size_bytes;
stream << " ("
<< (data.total_size_bytes != 0 ? (static_cast<double>(data.total_used_bytes) / static_cast<double>(data.total_size_bytes) *
100) : 0) << "%); space left: " << data.total_remaining_bytes << "]";
return stream;
}
};
template <typename T>
static inline constexpr blt::size_t aligned_size() noexcept
static constexpr size_t aligned_size() noexcept
{
return aligned_size(sizeof(NO_REF_T<T>));
}
static inline constexpr blt::size_t aligned_size(blt::size_t size) noexcept
{
return (size + (MAX_ALIGNMENT - 1)) & ~(MAX_ALIGNMENT - 1);
const auto bytes = align_bytes(sizeof(std::decay_t<T>));
if constexpr (blt::gp::detail::has_func_drop_v<detail::remove_cv_ref<T>>)
return bytes + align_bytes(sizeof(std::atomic_uint64_t*));
return bytes;
}
stack_allocator() = default;
@ -94,7 +87,8 @@ namespace blt::gp
stack_allocator(stack_allocator&& move) noexcept:
data_(std::exchange(move.data_, nullptr)), bytes_stored(std::exchange(move.bytes_stored, 0)), size_(std::exchange(move.size_, 0))
{}
{
}
stack_allocator& operator=(const stack_allocator& copy) = delete;
@ -121,8 +115,9 @@ namespace blt::gp
bytes_stored += stack.bytes_stored;
}
void copy_from(const stack_allocator& stack, blt::size_t bytes)
void copy_from(const stack_allocator& stack, const size_t bytes)
{
// TODO: add debug checks to these functions! (check for out of bounds copy)
if (bytes == 0)
return;
if (bytes + bytes_stored > size_)
@ -131,7 +126,17 @@ namespace blt::gp
bytes_stored += bytes;
}
void copy_from(blt::u8* data, blt::size_t bytes)
void copy_from(const stack_allocator& stack, const size_t bytes, const size_t offset)
{
if (bytes == 0)
return;
if (bytes + bytes_stored > size_)
expand(bytes + size_);
std::memcpy(data_ + bytes_stored, stack.data_ + (stack.bytes_stored - bytes - offset), bytes);
bytes_stored += bytes;
}
void copy_from(const u8* data, const size_t bytes)
{
if (bytes == 0 || data == nullptr)
return;
@ -141,85 +146,117 @@ 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;
std::memcpy(data, data_ + (bytes_stored - bytes), bytes);
}
template<typename T, typename NO_REF = NO_REF_T<T>>
template <typename 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));
std::memcpy(ptr, &t, sizeof(NO_REF));
using DecayedT = std::decay_t<T>;
static_assert(std::is_trivially_copyable_v<DecayedT>, "Type must be bitwise copyable!");
static_assert(alignof(DecayedT) <= detail::MAX_ALIGNMENT, "Type alignment must not be greater than the max alignment!");
const auto ptr = static_cast<char*>(allocate_bytes_for_size(aligned_size<DecayedT>()));
std::memcpy(ptr, &t, sizeof(DecayedT));
if constexpr (gp::detail::has_func_drop_v<detail::remove_cv_ref<T>>)
{
new(ptr + sizeof(DecayedT)) mem::pointer_storage<std::atomic_uint64_t>{nullptr};
}
}
template<typename T, typename NO_REF = NO_REF_T<T>>
template <typename 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));
using DecayedT = std::decay_t<T>;
static_assert(std::is_trivially_copyable_v<DecayedT>, "Type must be bitwise copyable!");
static_assert(alignof(DecayedT) <= detail::MAX_ALIGNMENT, "Type alignment must not be greater than the max alignment!");
constexpr auto size = aligned_size<DecayedT>();
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < size)
BLT_ABORT("Not enough bytes left to pop!");
throw std::runtime_error(("Not enough bytes left to pop!" __FILE__ ":") + std::to_string(__LINE__));
#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());
throw std::runtime_error(
"Not enough bytes in stack! " + std::to_string(bytes) + " bytes requested but only " + std::to_string(bytes_stored) +
(" bytes stored! (at " __FILE__ ":") + std::to_string(__LINE__));
#endif
return data_ + (bytes_stored - bytes);
}
template<typename T, typename NO_REF = NO_REF_T<T>>
T& from(blt::size_t bytes)
template <typename T>
T& from(const size_t bytes) const
{
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));
using DecayedT = std::decay_t<T>;
static_assert(std::is_trivially_copyable_v<DecayedT> && "Type must be bitwise copyable!");
static_assert(alignof(DecayedT) <= detail::MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
return *reinterpret_cast<DecayedT*>(from(aligned_size<DecayedT>() + bytes));
}
void pop_bytes(blt::size_t bytes)
[[nodiscard]] std::pair<u8*, mem::pointer_storage<std::atomic_uint64_t>&> access_pointer(const size_t bytes, const size_t type_size) const
{
const auto type_ref = from(bytes);
return {
type_ref, *std::launder(
reinterpret_cast<mem::pointer_storage<std::atomic_uint64_t>*>(type_ref + (type_size - align_bytes(
sizeof(std::atomic_uint64_t*)))))
};
}
[[nodiscard]] std::pair<u8*, mem::pointer_storage<std::atomic_uint64_t>&> access_pointer_forward(
const size_t bytes, const size_t type_size) const
{
const auto type_ref = data_ + bytes;
return {
type_ref, *std::launder(
reinterpret_cast<mem::pointer_storage<std::atomic_uint64_t>*>(type_ref + (type_size - align_bytes(
sizeof(std::atomic_uint64_t*)))))
};
}
template <typename T>
[[nodiscard]] std::pair<T&, mem::pointer_storage<std::atomic_uint64_t>&> access_pointer(const size_t bytes) const
{
auto& type_ref = from<T>(bytes);
return {
type_ref, *std::launder(
reinterpret_cast<mem::pointer_storage<std::atomic_uint64_t>*>(reinterpret_cast<char*>(&type_ref) +
align_bytes(sizeof(T))))
};
}
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());
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) +
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);
}
template<typename... Args>
void call_destructors()
{
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>))), ...);
}
to.copy_from(*this, aligned_bytes);
pop_bytes(aligned_bytes);
}
[[nodiscard]] bool empty() const noexcept
@ -227,39 +264,29 @@ namespace blt::gp
return bytes_stored == 0;
}
[[nodiscard]] blt::ptrdiff_t remaining_bytes_in_block() const noexcept
[[nodiscard]] ptrdiff_t remainder() 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]] size_t stored() const noexcept
{
return static_cast<blt::ptrdiff_t>(bytes_stored);
return bytes_stored;
}
[[nodiscard]] size_data_t size() const noexcept
{
size_data_t data;
data.total_used_bytes = bytes_stored;
data.total_size_bytes = size_;
data.total_remaining_bytes = remaining_bytes_in_block();
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
void resize(const size_t bytes)
{
return bytes_stored;
reserve(bytes);
bytes_stored = bytes;
}
[[nodiscard]] blt::size_t internal_storage_size() const
[[nodiscard]] size_t capacity() const
{
return size_;
}
@ -269,16 +296,22 @@ namespace blt::gp
bytes_stored = 0;
}
[[nodiscard]] auto* data() const
{
return data_;
}
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_);
@ -286,52 +319,61 @@ 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 = remainder();
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)
{
if constexpr (detail::has_func_drop_v<T>)
{
from<NO_REF_T<T >>(offset).drop();
}
}
blt::u8* data_ = nullptr;
u8* data_ = nullptr;
// place in the data_ array which has a free spot.
blt::size_t bytes_stored = 0;
blt::size_t size_ = 0;
size_t bytes_stored = 0;
size_t size_ = 0;
};
template <size_t Size>
struct ref_counted_type
{
explicit ref_counted_type(size_t* ref_count): ref_count(ref_count)
{
}
size_t* ref_count = nullptr;
u8 storage[Size]{};
static size_t* access(const void* ptr)
{
ref_counted_type<1> type{nullptr};
std::memcpy(&type, ptr, sizeof(size_t*));
return type.ref_count;
}
};
}
#endif //BLT_GP_STACK_H

88
include/blt/gp/sync.h Normal file
View File

@ -0,0 +1,88 @@
#pragma once
/*
* 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 BLT_GP_SYNC_H
#define BLT_GP_SYNC_H
#include <blt/std/types.h>
#include <blt/gp/fwdecl.h>
namespace blt::gp
{
class sync_t
{
public:
explicit sync_t(gp_program& program, fs::writer_t& writer);
virtual void trigger(u64 current_time) const;
sync_t& with_timer(u64 seconds)
{
m_timer_seconds = seconds;
return *this;
}
sync_t& every_generations(u64 generations)
{
m_generations = generations;
return *this;
}
sync_t& overwrite_file_on_write()
{
m_reset_to_start_of_file = true;
return *this;
}
sync_t& append_to_file_on_write()
{
m_reset_to_start_of_file = false;
return *this;
}
/**
* Save the state of the whole program instead of just the generation information.
*/
sync_t& whole_program()
{
m_whole_program = true;
return *this;
}
/**
* Only save the current generation to disk.
*/
sync_t& generation_only()
{
m_whole_program = false;
return *this;
}
~sync_t();
private:
gp_program* m_program;
fs::writer_t* m_writer;
std::optional<u64> m_timer_seconds;
std::optional<u64> m_generations;
bool m_reset_to_start_of_file = false;
bool m_whole_program = false;
};
}
#endif //BLT_GP_SYNC_H

241
include/blt/gp/threading.h Normal file
View File

@ -0,0 +1,241 @@
#pragma once
/*
* 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 BLT_GP_THREADING_H
#define BLT_GP_THREADING_H
#include <blt/std/types.h>
#include <blt/std/thread.h>
#include <thread>
#include <functional>
#include <atomic>
#include <type_traits>
namespace blt::gp
{
namespace detail
{
struct empty_callable
{
void operator()() const
{
}
};
}
template <typename EnumId>
class task_builder_t;
template <typename EnumId, typename Parallel, typename Single = detail::empty_callable>
class task_t
{
static_assert(std::is_enum_v<EnumId>, "Enum ID must be of enum type!");
static_assert(std::is_invocable_v<Parallel, int>, "Parallel must be invocable with exactly one argument (thread index)");
static_assert(std::is_invocable_v<Single>, "Single must be invocable with no arguments");
friend task_builder_t<EnumId>;
public:
task_t(const EnumId task_id, const Parallel& parallel, const Single& single): parallel(std::forward<Parallel>(parallel)),
single(std::forward<Single>(single)),
requires_single_sync(true), task_id(task_id)
{
}
explicit task_t(const EnumId task_id, const Parallel& parallel): parallel(std::forward<Parallel>(parallel)), single(detail::empty_callable{}),
task_id(task_id)
{
}
void call_parallel(size_t thread_index) const
{
parallel(thread_index);
}
void call_single() const
{
single();
}
[[nodiscard]] EnumId get_task_id() const
{
return task_id;
}
private:
const Parallel& parallel;
const Single& single;
bool requires_single_sync = false;
EnumId task_id;
};
template <typename EnumId, typename Parallel, typename Single = detail::empty_callable>
task_t(EnumId, Parallel, Single) -> task_t<EnumId, Parallel, Single>;
template <typename EnumId>
class task_builder_t
{
static_assert(std::is_enum_v<EnumId>, "Enum ID must be of enum type!");
using EnumInt = std::underlying_type_t<EnumId>;
public:
task_builder_t() = default;
template <typename... Tasks>
static std::function<void(barrier_t&, EnumId, size_t)> make_callable(Tasks&&... tasks)
{
return [&tasks...](barrier_t& sync_barrier, EnumId task, size_t thread_index)
{
call_jmp_table(sync_barrier, task, thread_index, tasks...);
};
}
private:
template <typename Task>
static void execute(barrier_t& sync_barrier, const size_t thread_index, Task&& task)
{
// sync_barrier.wait();
if (task.requires_single_sync)
{
if (thread_index == 0)
task.call_single();
sync_barrier.wait();
}
task.call_parallel(thread_index);
// sync_barrier.wait();
}
template <typename Task>
static bool call(barrier_t& sync_barrier, const EnumId current_task, const size_t thread_index, Task&& task)
{
if (static_cast<EnumInt>(current_task) == static_cast<EnumInt>(task.get_task_id()))
{
execute(sync_barrier, thread_index, std::forward<Task>(task));
return false;
}
return true;
}
template <typename... Tasks>
static void call_jmp_table(barrier_t& sync_barrier, const EnumId current_task, const size_t thread_index, Tasks&&... tasks)
{
if (static_cast<EnumInt>(current_task) >= sizeof...(tasks))
BLT_UNREACHABLE;
(call(sync_barrier, current_task, thread_index, std::forward<Tasks>(tasks)) && ...);
}
};
template <typename EnumId>
class thread_manager_t
{
static_assert(std::is_enum_v<EnumId>, "Enum ID must be of enum type!");
public:
explicit thread_manager_t(const size_t thread_count, std::function<void(barrier_t&, EnumId, size_t)> task_func,
const bool will_main_block = true): barrier(thread_count), will_main_block(will_main_block)
{
thread_callable = [this, task_func = std::move(task_func)](const size_t thread_index)
{
while (should_run)
{
barrier.wait();
if (tasks_remaining > 0)
task_func(barrier, tasks.back(), thread_index);
barrier.wait();
if (thread_index == 0)
{
if (this->will_main_block)
{
tasks.pop_back();
--tasks_remaining;
}
else
{
std::scoped_lock lock{task_lock};
tasks.pop_back();
--tasks_remaining;
}
}
}
};
for (size_t i = 0; i < will_main_block ? thread_count - 1 : thread_count; ++i)
threads.emplace_back(thread_callable, will_main_block ? i + 1 : i);
}
void execute() const
{
BLT_ASSERT(will_main_block &&
"You attempted to call this function without specifying that "
"you want an external blocking thread (try passing will_main_block = true)");
thread_callable(0);
}
void add_task(EnumId task)
{
if (will_main_block)
{
tasks.push_back(task);
++tasks_remaining;
}
else
{
std::scoped_lock lock(task_lock);
tasks.push_back(task);
++tasks_remaining;
}
}
bool has_tasks_left()
{
if (will_main_block)
{
return !tasks.empty();
}
std::scoped_lock lock{task_lock};
return tasks.empty();
}
~thread_manager_t()
{
should_run = false;
for (auto& thread : threads)
{
if (thread.joinable())
thread.join();
}
}
private:
[[nodiscard]] size_t thread_count() const
{
return will_main_block ? threads.size() + 1 : threads.size();
}
blt::barrier_t barrier;
std::atomic_bool should_run = true;
bool will_main_block;
std::vector<EnumId> tasks;
std::atomic_uint64_t tasks_remaining = 0;
std::vector<std::thread> threads;
std::mutex task_lock;
std::function<void(size_t)> thread_callable;
};
}
#endif //BLT_GP_THREADING_H

View File

@ -24,6 +24,7 @@
#include <blt/gp/tree.h>
#include <blt/gp/generators.h>
#include <blt/std/expected.h>
#include <blt/meta/config_generator.h>
namespace blt::gp
{
@ -33,12 +34,12 @@ namespace blt::gp
inline static constexpr double sum(const T& array)
{
double init = 0.0;
for (double i : array)
for (const double i : array)
init += i;
return init;
}
template<blt::size_t size, typename... Args>
template <size_t size, typename... Args>
static constexpr std::array<double, size> aggregate_array(Args... list)
{
std::array<double, size> data{list...};
@ -57,27 +58,84 @@ namespace blt::gp
class crossover_t
{
public:
struct crossover_point_t
struct point_info_t
{
blt::ptrdiff_t p1_crossover_point;
blt::ptrdiff_t p2_crossover_point;
ptrdiff_t point;
operator_info_t& type_operator_info;
};
struct config_t
{
// number of times crossover will try to pick a valid point in the tree. this is purely based on the return type of the operators
blt::u16 max_crossover_tries = 5;
// if we fail to find a point in the tree, should we search forward from the last point to the end of the operators?
bool should_crossover_try_forward = false;
// avoid selecting terminals when doing crossover
bool avoid_terminals = false;
u32 max_crossover_tries = 5;
// how many times the crossover function can fail before we will skip this operation.
u32 max_crossover_iterations = 10;
// if tree have fewer nodes than this number, they will not be considered for crossover
// should be at least 5 as crossover will not select the root node.
u32 min_tree_size = 5;
// used by the traverse version of get_crossover_point
// at each depth level, what chance do we have to exit with this as our point? or in other words what's the chance we continue traversing
// this is what this option configures.
f32 depth_multiplier = 0.5;
// how often should we select terminals over functions. By default, we only allow selection of terminals 10% of the time
// this applies to both types of crossover point functions. Traversal will use the parent if it should not pick a terminal.
f32 terminal_chance = 0.1;
// use traversal to select point instead of random selection
bool traverse = false;
BLT_MAKE_SETTER_LVALUE(u32, max_crossover_tries);
BLT_MAKE_SETTER_LVALUE(u32, max_crossover_iterations);
BLT_MAKE_SETTER_LVALUE(u32, min_tree_size);
BLT_MAKE_SETTER_LVALUE(f32, depth_multiplier);
BLT_MAKE_SETTER_LVALUE(f32, terminal_chance);
BLT_MAKE_SETTER_LVALUE(bool, traverse);
};
crossover_t() = default;
explicit crossover_t(const config_t& config): config(config)
{}
{
}
std::optional<crossover_t::crossover_point_t> get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const;
/**
* Apply crossover to a set of parents. Note: c1 and c2 are already filled with thier respective parent's elements.
* @return true if the crossover succeeded, otherwise return false will erase progress.
*/
virtual bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) = 0;
[[nodiscard]] const config_t& get_config() const
{
return config;
}
virtual ~crossover_t() = default;
protected:
config_t config;
};
/**
* Base class for crossover which performs basic subtree crossover on two random nodes in the parent tree
*/
class subtree_crossover_t : public crossover_t
{
public:
struct crossover_point_t
{
tree_t::subtree_point_t p1_crossover_point;
tree_t::subtree_point_t p2_crossover_point;
};
subtree_crossover_t(): crossover_t(config_t{})
{
}
explicit subtree_crossover_t(const config_t& config): crossover_t(config)
{
}
[[nodiscard]] std::optional<crossover_point_t> get_crossover_point(const tree_t& c1, const tree_t& c2) const;
[[nodiscard]] std::optional<crossover_point_t> get_crossover_point_traverse(const tree_t& c1, const tree_t& c2) const;
/**
* child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover.
@ -85,14 +143,39 @@ namespace blt::gp
* @param program reference to the global program container responsible for managing these trees
* @param p1 reference to the first parent
* @param p2 reference to the second parent
* @return expected pair of child otherwise returns error enum
* @param c1 reference to output child 1
* @param c2 reference to output child 2
* @return true if function succeeded, otherwise false
*/
virtual bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2); // NOLINT
virtual bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) override; // NOLINT
virtual ~crossover_t() = default;
~subtree_crossover_t() override = default;
protected:
config_t config;
[[nodiscard]] std::optional<tree_t::subtree_point_t> get_point_traverse_retry(const tree_t& t, std::optional<type_id> type) const;
};
class one_point_crossover_t : public crossover_t
{
public:
one_point_crossover_t(): crossover_t(config_t{})
{
}
explicit one_point_crossover_t(const config_t& config): crossover_t(config)
{
}
bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) override;
};
class advanced_crossover_t : public crossover_t
{
advanced_crossover_t(): crossover_t(config_t{})
{
}
public:
bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) override;
};
class mutation_t
@ -106,7 +189,8 @@ namespace blt::gp
std::reference_wrapper<tree_generator_t> generator;
config_t(tree_generator_t& generator): generator(generator) // NOLINT
{}
{
}
config_t();
};
@ -114,12 +198,13 @@ namespace blt::gp
mutation_t() = default;
explicit mutation_t(const config_t& config): config(config)
{}
{
}
virtual bool apply(gp_program& program, const tree_t& p, tree_t& c);
// returns the point after the mutation
blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node);
size_t mutate_point(gp_program& program, tree_t& c, tree_t::subtree_point_t node) const;
virtual ~mutation_t() = default;
@ -130,7 +215,7 @@ namespace blt::gp
class advanced_mutation_t : public mutation_t
{
public:
enum class mutation_operator : blt::i32
enum class mutation_operator : i32
{
EXPRESSION, // Generate a new random expression
ADJUST, // adjust the value of the type. (if it is a function it will mutate it to a different one)
@ -143,7 +228,8 @@ namespace blt::gp
advanced_mutation_t() = default;
explicit advanced_mutation_t(const config_t& config): mutation_t(config)
{}
{
}
bool apply(gp_program& program, const tree_t& p, tree_t& c) final;
@ -155,19 +241,19 @@ namespace blt::gp
private:
static constexpr auto operators_size = static_cast<blt::i32>(mutation_operator::END);
private:
// this value is adjusted inversely to the size of the tree.
double per_node_mutation_chance = 5.0;
static constexpr std::array<double, operators_size> mutation_operator_chances = detail::aggregate_array<operators_size>(
0.25, // EXPRESSION
0.15, // ADJUST
0.01, // SUB_FUNC
0.01, // JUMP_FUNC
0.05 // COPY
0.20, // ADJUST
0.05, // SUB_FUNC
0.15, // JUMP_FUNC
0.10 // COPY
);
};
}
#endif //BLT_GP_TRANSFORMERS_H

View File

@ -19,28 +19,82 @@
#ifndef BLT_GP_TREE_H
#define BLT_GP_TREE_H
#include <blt/gp/util/meta.h>
#include <blt/gp/typesystem.h>
#include <blt/gp/stack.h>
#include <blt/gp/fwdecl.h>
#include <blt/std/types.h>
#include <blt/fs/fwddecl.h>
#include <utility>
#include <stack>
#include <ostream>
#include <atomic>
namespace blt::gp
{
// TODO: i feel like this should be in its own class
struct operator_special_flags
{
explicit operator_special_flags(const bool is_ephemeral = false, const bool has_ephemeral_drop = false): m_ephemeral(is_ephemeral),
m_ephemeral_drop(has_ephemeral_drop)
{
}
[[nodiscard]] bool is_ephemeral() const
{
return m_ephemeral;
}
[[nodiscard]] bool has_ephemeral_drop() const
{
return m_ephemeral_drop;
}
private:
bool m_ephemeral : 1;
bool m_ephemeral_drop : 1;
};
static_assert(sizeof(operator_special_flags) == 1, "Size of operator flags struct is expected to be 1 byte!");
struct op_container_t
{
op_container_t(blt::size_t type_size, operator_id id, bool is_value):
type_size(type_size), id(id), is_value(is_value)
{}
op_container_t(const size_t type_size, const operator_id id, const bool is_value, const operator_special_flags flags):
m_type_size(type_size), m_id(id), m_is_value(is_value), m_flags(flags)
{
}
blt::size_t type_size;
operator_id id;
bool is_value;
[[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_ephemeral_drop() const
{
return m_flags.has_ephemeral_drop();
}
[[nodiscard]] operator_special_flags get_flags() const
{
return m_flags;
}
friend bool operator==(const op_container_t& a, const op_container_t& b);
private:
size_t m_type_size;
operator_id m_id;
bool m_is_value;
operator_special_flags m_flags;
};
class evaluation_context
@ -48,20 +102,174 @@ namespace blt::gp
public:
explicit evaluation_context() = default;
blt::gp::stack_allocator values;
stack_allocator values;
};
inline size_t accumulate_type_sizes(const detail::op_iter_t begin, const detail::op_iter_t end)
{
size_t total = 0;
for (auto it = begin; it != end; ++it)
{
if (it->is_value())
total += it->type_size();
}
return total;
}
template <typename T>
class evaluation_ref
{
public:
explicit evaluation_ref(const bool ephemeral, T& value, evaluation_context& context): m_value(&value), m_context(&context)
{
if (ephemeral)
m_value.bit(0, true);
}
evaluation_ref(const evaluation_ref& copy) = delete;
evaluation_ref& operator=(const evaluation_ref& copy) = delete;
evaluation_ref(evaluation_ref&& move) noexcept : m_value(move.m_value), m_context(move.m_context)
{
move.m_value = nullptr;
move.m_context = nullptr;
}
evaluation_ref& operator=(evaluation_ref&& move) noexcept
{
m_value = std::exchange(m_value, move.m_value);
m_context = std::exchange(m_context, move.m_context);
return *this;
}
T& get()
{
return *m_value;
}
const T& get() const
{
return *m_value;
}
explicit operator T&()
{
return *m_value;
}
explicit operator T&() const
{
return *m_value;
}
T* operator->()
{
return m_value.get();
}
~evaluation_ref()
{
if constexpr (detail::has_func_drop_v<detail::remove_cv_ref<T>>)
{
if (m_value.get() != nullptr)
{
if (!m_value.bit(0))
m_value->drop();
m_context->values.reset();
}
}
}
private:
mem::pointer_storage<T> m_value;
evaluation_context* m_context;
};
class tree_t
{
public:
explicit tree_t(gp_program& program);
struct subtree_point_t
{
ptrdiff_t pos;
type_id type;
tree_t(const tree_t& copy) = default;
subtree_point_t() = default;
explicit subtree_point_t(const ptrdiff_t pos): pos(pos), type(0)
{
}
subtree_point_t(const ptrdiff_t pos, const type_id type): pos(pos), type(type)
{
}
};
struct child_t
{
ptrdiff_t start;
// one past the end
ptrdiff_t end;
};
struct byte_only_transaction_t
{
byte_only_transaction_t(tree_t& tree, const size_t bytes): tree(tree), data(nullptr), bytes(bytes)
{
move(bytes);
}
explicit byte_only_transaction_t(tree_t& tree): tree(tree), data(nullptr), bytes(0)
{
}
byte_only_transaction_t(const byte_only_transaction_t& copy) = delete;
byte_only_transaction_t& operator=(const byte_only_transaction_t& copy) = delete;
byte_only_transaction_t(byte_only_transaction_t&& move) noexcept: tree(move.tree), data(std::exchange(move.data, nullptr)),
bytes(std::exchange(move.bytes, 0))
{
}
byte_only_transaction_t& operator=(byte_only_transaction_t&& move) noexcept = delete;
void move(size_t bytes_to_move);
[[nodiscard]] bool empty() const
{
return bytes == 0;
}
~byte_only_transaction_t()
{
if (!empty())
{
tree.values.copy_from(data, bytes);
bytes = 0;
}
}
private:
tree_t& tree;
u8* data;
size_t bytes;
};
explicit tree_t(gp_program& program): m_program(&program)
{
}
tree_t(const tree_t& copy): m_program(copy.m_program)
{
copy_fast(copy);
}
tree_t& operator=(const tree_t& copy)
{
if (this == &copy)
return *this;
m_program = copy.m_program;
copy_fast(copy);
return *this;
}
@ -69,18 +277,62 @@ namespace blt::gp
/**
* This function copies the data from the provided tree, will attempt to reserve and copy in one step.
* will avoid reallocation if enough space is already present.
*
* This function is meant to copy into and replaces data inside the tree.
*/
void copy_fast(const tree_t& copy)
{
if (this == &copy)
return;
values.reserve(copy.values.internal_storage_size());
operations.reserve(copy.operations.size());
auto copy_it = copy.operations.begin();
auto op_it = operations.begin();
size_t total_op_bytes = 0;
size_t total_copy_bytes = 0;
for (; op_it != operations.end(); ++op_it)
{
if (copy_it == copy.operations.end())
break;
if (copy_it->is_value())
{
copy.handle_refcount_increment(copy_it, total_copy_bytes);
total_copy_bytes += copy_it->type_size();
}
if (op_it->is_value())
{
handle_refcount_decrement(op_it, total_op_bytes);
total_op_bytes += op_it->type_size();
}
*op_it = *copy_it;
++copy_it;
}
const auto op_it_cpy = op_it;
for (; op_it != operations.end(); ++op_it)
{
if (op_it->is_value())
{
handle_refcount_decrement(op_it, total_op_bytes);
total_op_bytes += op_it->type_size();
}
}
operations.erase(op_it_cpy, operations.end());
for (; copy_it != copy.operations.end(); ++copy_it)
{
if (copy_it->is_value())
{
copy.handle_refcount_increment(copy_it, total_copy_bytes);
total_copy_bytes += copy_it->type_size();
}
operations.emplace_back(*copy_it);
}
values.reserve(copy.values.stored());
values.reset();
values.insert(copy.values);
operations.clear();
operations.reserve(copy.operations.size());
operations.insert(operations.begin(), copy.operations.begin(), copy.operations.end());
}
tree_t(tree_t&& move) = default;
@ -89,111 +341,407 @@ namespace blt::gp
void clear(gp_program& program);
struct child_t
{
blt::ptrdiff_t start;
// one past the end
blt::ptrdiff_t end;
};
void insert_operator(size_t index, const op_container_t& container);
[[nodiscard]] inline tracked_vector<op_container_t>& get_operations()
void insert_operator(const op_container_t& container)
{
return operations;
operations.emplace_back(container);
handle_operator_inserted(operations.back());
}
[[nodiscard]] inline const tracked_vector<op_container_t>& get_operations() const
template <typename... Args>
void emplace_operator(Args&&... args)
{
return operations;
operations.emplace_back(std::forward<Args>(args)...);
handle_operator_inserted(operations.back());
}
[[nodiscard]] inline blt::gp::stack_allocator& get_values()
{
return values;
}
[[nodiscard]] inline const blt::gp::stack_allocator& get_values() const
{
return values;
}
evaluation_context& evaluate(void* context) const
{
return (*func)(*this, context);
}
blt::size_t get_depth(gp_program& program);
size_t get_depth(gp_program& program) const;
/**
* Helper template for returning the result of the last evaluation
* Selects a random index inside this tree's operations stack
* @param terminal_chance if we select a terminal this is the chance we will actually pick it, otherwise continue the loop.
*/
template<typename T>
T get_evaluation_value(evaluation_context& context)
[[nodiscard]] subtree_point_t select_subtree(double terminal_chance = 0.1) const;
/**
* Selects a random index inside the tree's operations stack, with a limit on the max number of times we will attempt to select this point.
* @param type type to find
* @param max_tries maximum number of times we are allowed to select a tree without finding a corresponding type.
* @param terminal_chance if we select a terminal this is the chance that we will actually pick it
*/
[[nodiscard]] std::optional<subtree_point_t> select_subtree(type_id type, u32 max_tries = 5, double terminal_chance = 0.1) const;
/**
* Select an index by traversing through the tree structure
* @param terminal_chance if we select a terminal this is the chance that we will actually pick it.
* @param depth_multiplier this controls how the depth contributes to the chance to exit.
* By default, a depth of 3.5 will have a 50% chance of returning the current index.
*/
[[nodiscard]] subtree_point_t select_subtree_traverse(double terminal_chance = 0.1, double depth_multiplier = 0.6) const;
/**
* SSelect an index by traversing through the tree structure, with a limit on the max number of times we will attempt to select this point.
* @param type type to find
* @param max_tries maximum number of times we are allowed to select a tree without finding a corresponding type.
* @param terminal_chance if we select a terminal this is the chance that we will actually pick it
* @param depth_multiplier this controls how the depth contributes to the chance to exit.
* By default, a depth of 3.5 will have a 50% chance of returning the current index.
*/
[[nodiscard]] std::optional<subtree_point_t> select_subtree_traverse(type_id type, u32 max_tries = 5, double terminal_chance = 0.1,
double depth_multiplier = 0.6) const;
/**
* Copies the subtree found at point into the provided out params
* @param point subtree point
* @param extent how far the subtree extends
* @param operators vector for storing subtree operators
* @param stack stack for storing subtree values
*/
void copy_subtree(subtree_point_t point, ptrdiff_t extent, tracked_vector<op_container_t>& operators, stack_allocator& stack);
/**
* Copies the subtree found at point into the provided out params
* @param point subtree point
* @param operators vector for storing subtree operators
* @param stack stack for storing subtree values
*/
void copy_subtree(const subtree_point_t point, tracked_vector<op_container_t>& operators, stack_allocator& stack)
{
return context.values.pop<T>();
copy_subtree(point, find_endpoint(point.pos), operators, stack);
}
void copy_subtree(const subtree_point_t point, const ptrdiff_t extent, tree_t& out_tree)
{
copy_subtree(point, extent, out_tree.operations, out_tree.values);
}
void copy_subtree(const subtree_point_t point, tree_t& out_tree)
{
copy_subtree(point, find_endpoint(point.pos), out_tree);
}
void copy_subtree(const child_t subtree, tree_t& out_tree)
{
copy_subtree(subtree_point_t{subtree.start}, subtree.end, out_tree);
}
void swap_subtrees(child_t our_subtree, tree_t& other_tree, child_t other_subtree);
/**
* Swaps the subtrees between this tree and the other tree
* @param our_subtree
* @param other_tree
* @param other_subtree
*/
void swap_subtrees(subtree_point_t our_subtree, tree_t& other_tree, subtree_point_t other_subtree);
/**
* Replaces the point inside our tree with a new tree provided to this function.
* Uses the extent instead of calculating it for removing the existing subtree
* This can be used if you already have child tree information, such as when using @code find_child_extends@endcode
* @param point point to replace at
* @param extent extend of the subtree (child tree)
* @param other_tree other tree to replace with
*/
void replace_subtree(subtree_point_t point, ptrdiff_t extent, tree_t& other_tree);
/**
* Replaces the point inside our tree with a new tree provided to this function
* @param point point to replace at
* @param other_tree other tree to replace with
*/
void replace_subtree(const subtree_point_t point, tree_t& other_tree)
{
replace_subtree(point, find_endpoint(point.pos), other_tree);
}
/**
* Helper template for returning the result of the last evaluation
* Deletes the subtree at a point, bounded by extent. This is useful if you already know the size of the child tree
* Note: if you provide an incorrectly sized extent this will create UB within the GP program
* extent must be one past the last element in the subtree, as returned by all helper functions here.
* @param point point to delete from
* @param extent end point of the tree
*/
template<typename T>
T& get_evaluation_ref(evaluation_context& context)
void delete_subtree(subtree_point_t point, ptrdiff_t extent);
/**
* Deletes the subtree at a point
* @param point point of subtree to recursively delete
*/
void delete_subtree(const subtree_point_t point)
{
return context.values.from<T>(0);
delete_subtree(point, find_endpoint(point.pos));
}
void delete_subtree(const child_t subtree)
{
delete_subtree(subtree_point_t{subtree.start}, subtree.end);
}
/**
* Helper template for returning the result of evaluation (this calls it)
* Insert a subtree before the specified point
* @param point point to insert into
* @param other_tree the tree to insert
* @return point + other_tree.size()
*/
template<typename T>
T get_evaluation_value(void* context)
ptrdiff_t insert_subtree(subtree_point_t point, tree_t& other_tree);
/**
* temporarily moves the last bytes amount of data from the current stack. this can be useful for if you are going to do a lot
* of consecutive operations on the tree as this will avoid extra copy + reinsert.
* The object returned by this function will automatically move the data back in when it goes out of scope.
* @param operator_index operator index to move from. this is inclusive
*/
void temporary_move(const size_t)
{
auto& results = evaluate(context);
return results.values.pop<T>();
// return obt_move_t{*this, operator_index};
}
void print(gp_program& program, std::ostream& output, bool print_literals = true, bool pretty_indent = false,
bool include_types = false) const;
void modify_operator(size_t point, operator_id new_id, std::optional<type_id> return_type = {});
bool check(gp_program& program, void* context) const;
/**
* User function for evaluating this tree using a context reference. This function should only be used if the tree is expecting the context value
* This function returns a copy of your value, if it is too large for the stack, or you otherwise need a reference, please use the corresponding
* get_evaluation_ref function!
*/
template <typename T, typename Context>
T get_evaluation_value(const Context& context) const
{
auto& ctx = evaluate(context);
auto val = ctx.values.template from<T>(0);
evaluation_ref<T> ref{operations.front().get_flags().is_ephemeral(), val, ctx};
return ref.get();
}
void find_child_extends(gp_program& program, tracked_vector<child_t>& vec, blt::size_t parent_node, blt::size_t argc) const;
/**
* User function for evaluating this tree without a context reference. This function should only be used if the tree is expecting the context value
* This function returns a copy of your value, if it is too large for the stack, or you otherwise need a reference, please use the corresponding
* get_evaluation_ref function!
*/
template <typename T>
T get_evaluation_value() const
{
auto& ctx = evaluate();
auto val = ctx.values.from<T>(0);
evaluation_ref<T> ref{operations.front().get_flags().is_ephemeral(), val, ctx};
return ref.get();
}
blt::ptrdiff_t find_endpoint(blt::gp::gp_program& program, blt::ptrdiff_t start) const;
/**
* User function for evaluating the tree with context returning a reference to the value.
* The class returned is used to automatically drop the value when you are done using it
*/
template <typename T, typename Context>
evaluation_ref<T> get_evaluation_ref(const Context& context) const
{
auto& ctx = evaluate(context);
auto& val = ctx.values.template from<T>(0);
return evaluation_ref<T>{operations.front().get_flags().is_ephemeral(), val, ctx};
}
blt::ptrdiff_t find_parent(blt::gp::gp_program& program, blt::ptrdiff_t start) const;
/**
* User function for evaluating the tree without context returning a reference to the value.
* The class returned is used to automatically drop the value when you are done using it
*/
template <typename T>
evaluation_ref<T> get_evaluation_ref() const
{
auto& ctx = evaluate();
auto& val = ctx.values.from<T>(0);
return evaluation_ref<T>{operations.front().get_flags().is_ephemeral(), val, ctx};
}
void print(std::ostream& out, bool print_literals = true, bool pretty_indent = false, bool include_types = false,
ptrdiff_t marked_index = -1) const;
bool check(void* context) const;
void find_child_extends(tracked_vector<child_t>& vec, blt::size_t parent_node, blt::size_t argc) const;
// places one past the end of the child. so it's [start, end)
[[nodiscard]] ptrdiff_t find_endpoint(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));
return total_value_bytes(operations.begin() + static_cast<ptrdiff_t>(begin),
operations.begin() + static_cast<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());
return total_value_bytes(operations.begin() + static_cast<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());
}
[[nodiscard]] size_t size() const
{
return operations.size();
}
[[nodiscard]] const op_container_t& get_operator(const size_t point) const
{
return operations[point];
}
[[nodiscard]] subtree_point_t subtree_from_point(ptrdiff_t point) const;
template <typename Context, typename... Operators>
static auto make_execution_lambda(size_t call_reserve_size, Operators&... operators)
{
return [call_reserve_size, &operators...](const tree_t& tree, void* context) -> evaluation_context&
{
const auto& ops = tree.operations;
const auto& vals = tree.values;
thread_local evaluation_context results{};
results.values.reset();
results.values.reserve(call_reserve_size);
size_t total_so_far = 0;
for (const auto& operation : iterate(ops).rev())
{
if (operation.is_value())
{
total_so_far += operation.type_size();
results.values.copy_from(vals.from(total_so_far), operation.type_size());
continue;
}
call_jmp_table<Context>(operation.id(), context, results.values, results.values, operators...);
}
return results;
};
}
[[nodiscard]] size_t required_size() const;
void to_byte_array(std::byte* out) const;
void to_file(fs::writer_t& file) const;
void from_byte_array(const std::byte* in);
void from_file(fs::reader_t& file);
~tree_t()
{
clear(*m_program);
}
static tree_t& get_thread_local(gp_program& program);
friend bool operator==(const tree_t& a, const tree_t& b);
friend bool operator!=(const tree_t& a, const tree_t& b)
{
return !(a == b);
}
private:
void handle_operator_inserted(const op_container_t& op);
void handle_ptr_empty(const mem::pointer_storage<std::atomic_uint64_t>& ptr, u8* data, operator_id id) const;
template <typename Iter>
void handle_refcount_decrement(const Iter iter, const size_t forward_bytes) const
{
if (iter->get_flags().is_ephemeral() && iter->has_ephemeral_drop())
{
auto [val, ptr] = values.access_pointer_forward(forward_bytes, iter->type_size());
--*ptr;
if (*ptr == 0)
handle_ptr_empty(ptr, val, iter->id());
}
}
template <typename Iter>
void handle_refcount_increment(const Iter iter, const size_t forward_bytes) const
{
if (iter->get_flags().is_ephemeral() && iter->has_ephemeral_drop())
{
auto [_, ptr] = values.access_pointer_forward(forward_bytes, iter->type_size());
++*ptr;
}
}
template <typename T, std::enable_if_t<!(std::is_pointer_v<T> || std::is_null_pointer_v<T>), bool> = true>
[[nodiscard]] evaluation_context& evaluate(const T& context) const
{
return evaluate(const_cast<void*>(static_cast<const void*>(&context)));
}
[[nodiscard]] evaluation_context& evaluate() const
{
return evaluate(nullptr);
}
[[nodiscard]] evaluation_context& evaluate(void* ptr) const;
tracked_vector<op_container_t> operations;
blt::gp::stack_allocator values;
detail::eval_func_t* func;
stack_allocator values;
gp_program* m_program;
/*
* Static members
* --------------
*/
protected:
template <typename Context, typename Operator>
static void execute(void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if constexpr (std::is_same_v<detail::remove_cv_ref<typename Operator::First_Arg>, Context>)
{
write_stack.push(operation(context, read_stack));
}
else
{
write_stack.push(operation(read_stack));
}
}
template <typename Context, size_t id, typename Operator>
static bool call(const size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if (id == op)
{
execute<Context>(context, write_stack, read_stack, operation);
return false;
}
return true;
}
template <typename Context, typename... Operators, size_t... operator_ids>
static void call_jmp_table_internal(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
std::integer_sequence<size_t, operator_ids...>, Operators&... operators)
{
if (op >= sizeof...(operator_ids))
{
BLT_UNREACHABLE;
}
(call<Context, operator_ids>(op, context, write_stack, read_stack, operators) && ...);
}
template <typename Context, typename... Operators>
static void call_jmp_table(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
Operators&... operators)
{
call_jmp_table_internal<Context>(op, context, write_stack, read_stack, std::index_sequence_for<Operators...>(), operators...);
}
};
struct fitness_t
@ -201,7 +749,7 @@ namespace blt::gp
double raw_fitness = 0;
double standardized_fitness = 0;
double adjusted_fitness = 0;
blt::i64 hits = 0;
i64 hits = 0;
};
struct individual_t
@ -220,10 +768,12 @@ namespace blt::gp
individual_t() = delete;
explicit individual_t(tree_t&& tree): tree(std::move(tree))
{}
{
}
explicit individual_t(const tree_t& tree): tree(tree)
{}
{
}
individual_t(const individual_t&) = default;
@ -232,44 +782,12 @@ namespace blt::gp
individual_t& operator=(const individual_t&) = delete;
individual_t& operator=(individual_t&&) = default;
};
struct population_stats
friend bool operator==(const individual_t& a, const individual_t& b);
friend bool operator!=(const individual_t& a, const individual_t& b)
{
population_stats() = default;
population_stats(const population_stats& copy):
overall_fitness(copy.overall_fitness.load()), average_fitness(copy.average_fitness.load()), best_fitness(copy.best_fitness.load()),
worst_fitness(copy.worst_fitness.load())
{
normalized_fitness.reserve(copy.normalized_fitness.size());
for (auto v : copy.normalized_fitness)
normalized_fitness.push_back(v);
}
population_stats(population_stats&& move) noexcept:
overall_fitness(move.overall_fitness.load()), average_fitness(move.average_fitness.load()), best_fitness(move.best_fitness.load()),
worst_fitness(move.worst_fitness.load()), normalized_fitness(std::move(move.normalized_fitness))
{
move.overall_fitness = 0;
move.average_fitness = 0;
move.best_fitness = 0;
move.worst_fitness = 0;
}
std::atomic<double> overall_fitness = 0;
std::atomic<double> average_fitness = 0;
std::atomic<double> best_fitness = 0;
std::atomic<double> worst_fitness = 1;
tracked_vector<double> normalized_fitness{};
void clear()
{
overall_fitness = 0;
average_fitness = 0;
best_fitness = 0;
worst_fitness = 0;
normalized_fitness.clear();
return !(a == b);
}
};
@ -280,7 +798,8 @@ namespace blt::gp
{
public:
population_tree_iterator(tracked_vector<individual_t>& ind, blt::size_t pos): ind(ind), pos(pos)
{}
{
}
auto begin()
{
@ -303,12 +822,12 @@ namespace blt::gp
return {ind, ++pos};
}
tree_t& operator*()
tree_t& operator*() const
{
return ind[pos].tree;
}
tree_t& operator->()
tree_t& operator->() const
{
return ind[pos].tree;
}

View File

@ -30,14 +30,14 @@
namespace blt::gp
{
struct operator_id : integer_type<blt::size_t>
struct operator_id : integer_type<u64>
{
using integer_type<blt::size_t>::integer_type;
using integer_type::integer_type;
};
struct type_id : integer_type<blt::size_t>
struct type_id : integer_type<u64>
{
using integer_type<blt::size_t>::integer_type;
using integer_type::integer_type;
};
class type
@ -46,33 +46,40 @@ 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>(), detail::has_func_drop_v<detail::remove_cv_ref<T>>);
}
[[nodiscard]] inline blt::size_t size() const
[[nodiscard]] size_t size() const
{
return size_;
}
[[nodiscard]] inline type_id id() const
[[nodiscard]] type_id id() const
{
return id_;
}
[[nodiscard]] inline std::string_view name() const
[[nodiscard]] std::string_view name() const
{
return name_;
}
[[nodiscard]] bool has_ephemeral_drop() const
{
return has_ephemeral_drop_;
}
private:
type(size_t size, type_id id, std::string_view name): size_(size), id_(id), name_(name)
{}
type(const size_t size, const type_id id, const std::string_view name, const bool has_ephemeral_drop): size_(size), id_(id), name_(name),
has_ephemeral_drop_(has_ephemeral_drop)
{
}
blt::size_t size_{};
size_t size_{};
type_id id_{};
std::string name_{};
bool has_ephemeral_drop_ = false;
};
/**
@ -85,7 +92,7 @@ namespace blt::gp
type_provider() = default;
template <typename T>
inline void register_type()
void register_type()
{
if (has_type<T>())
return;
@ -95,17 +102,18 @@ namespace blt::gp
}
template <typename T>
inline type get_type()
type get_type()
{
return types[blt::type_string_raw<T>()];
}
template <typename T>
inline bool has_type(){
bool has_type()
{
return types.find(blt::type_string_raw<T>()) != types.end();
}
inline type get_type(type_id id)
type get_type(type_id id)
{
return types_from_id[id];
}
@ -115,7 +123,7 @@ namespace blt::gp
* @param engine
* @return
*/
inline type select_type(std::mt19937_64& engine)
type select_type(std::mt19937_64& engine)
{
std::uniform_int_distribution dist(0ul, types.size() - 1);
auto offset = dist(engine);
@ -126,8 +134,8 @@ namespace blt::gp
}
private:
blt::hashmap_t<std::string, type> types;
blt::expanding_buffer<type> types_from_id;
hashmap_t<std::string, type> types;
expanding_buffer<type> types_from_id;
};
}

View File

@ -0,0 +1,66 @@
#pragma once
/*
* 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 BLT_GP_UTIL_META_H
#define BLT_GP_UTIL_META_H
#include <type_traits>
namespace blt::gp::detail
{
template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename...>
struct first_arg;
template <typename First, typename... Args>
struct first_arg<First, Args...>
{
using type = First;
};
template <>
struct first_arg<>
{
using type = void;
};
template <bool b, typename... types>
struct is_same;
template <typename... types>
struct is_same<true, types...> : std::false_type
{
};
template <typename... types>
struct is_same<false, types...> : std::is_same<types...>
{
};
template <typename... types>
constexpr bool is_same_v = is_same<sizeof...(types) == 0, types...>::value;
struct empty_t
{
};
}
#endif //BLT_GP_UTIL_META_H

View File

@ -0,0 +1,216 @@
#pragma once
/*
* 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 BLT_GP_UTIL_STATISTICS_H
#define BLT_GP_UTIL_STATISTICS_H
#include <blt/gp/util/trackers.h>
#include <blt/gp/allocator.h>
#include <blt/gp/fwdecl.h>
namespace blt::gp
{
struct confusion_matrix_t
{
public:
confusion_matrix_t() = default;
confusion_matrix_t& is_A_predicted_A()
{
++is_A_pred_A;
return *this;
}
confusion_matrix_t& is_A_predicted_B()
{
++is_A_pred_B;
return *this;
}
confusion_matrix_t& is_B_predicted_A()
{
++is_B_pred_A;
return *this;
}
confusion_matrix_t& is_B_predicted_B()
{
++is_B_pred_B;
return *this;
}
confusion_matrix_t& set_name_a(const std::string& name_a)
{
name_A = name_a;
return *this;
}
confusion_matrix_t& set_name_b(const std::string& name_b)
{
name_B = name_b;
return *this;
}
[[nodiscard]] u64 get_is_a_pred_a() const
{
return is_A_pred_A;
}
[[nodiscard]] u64 get_is_a_pred_b() const
{
return is_A_pred_B;
}
[[nodiscard]] u64 get_is_b_pred_b() const
{
return is_B_pred_B;
}
[[nodiscard]] u64 get_is_b_pred_a() const
{
return is_B_pred_A;
}
[[nodiscard]] u64 get_hits() const
{
return is_A_pred_A + is_B_pred_B;
}
[[nodiscard]] u64 get_misses() const
{
return is_B_pred_A + is_A_pred_B;
}
[[nodiscard]] u64 get_total() const
{
return get_hits() + get_misses();
}
[[nodiscard]] double get_percent_hit() const
{
return static_cast<double>(get_hits()) / static_cast<double>(get_total());
}
confusion_matrix_t& operator+=(const confusion_matrix_t& op)
{
is_A_pred_A += op.is_A_pred_A;
is_B_pred_A += op.is_B_pred_A;
is_A_pred_B += op.is_A_pred_B;
is_B_pred_B += op.is_B_pred_B;
return *this;
}
confusion_matrix_t& operator/=(const u64 val)
{
is_A_pred_A /= val;
is_B_pred_A /= val;
is_A_pred_B /= val;
is_B_pred_B /= val;
return *this;
}
friend confusion_matrix_t operator+(const confusion_matrix_t& op1, const confusion_matrix_t& op2)
{
confusion_matrix_t result = op1;
result += op2;
return result;
}
friend confusion_matrix_t operator/(const confusion_matrix_t& op1, const u64 val)
{
confusion_matrix_t result = op1;
result /= val;
return result;
}
friend bool operator<(const confusion_matrix_t& a, const confusion_matrix_t& b)
{
return a.get_percent_hit() < b.get_percent_hit();
}
friend bool operator>(const confusion_matrix_t& a, const confusion_matrix_t& b)
{
return a.get_percent_hit() > b.get_percent_hit();
}
[[nodiscard]] std::string pretty_print(const std::string& table_name = "Confusion Matrix") const;
private:
u64 is_A_pred_A = 0;
u64 is_A_pred_B = 0;
u64 is_B_pred_B = 0;
u64 is_B_pred_A = 0;
std::string name_A = "A";
std::string name_B = "B";
};
struct population_stats
{
population_stats() = default;
population_stats(const population_stats& copy):
overall_fitness(copy.overall_fitness.load()), average_fitness(copy.average_fitness.load()), best_fitness(copy.best_fitness.load()),
worst_fitness(copy.worst_fitness.load())
{
normalized_fitness.reserve(copy.normalized_fitness.size());
for (auto v : copy.normalized_fitness)
normalized_fitness.push_back(v);
}
population_stats(population_stats&& move) noexcept:
overall_fitness(move.overall_fitness.load()), average_fitness(move.average_fitness.load()), best_fitness(move.best_fitness.load()),
worst_fitness(move.worst_fitness.load()), normalized_fitness(std::move(move.normalized_fitness))
{
move.overall_fitness = 0;
move.average_fitness = 0;
move.best_fitness = 0;
move.worst_fitness = 0;
}
std::atomic<double> overall_fitness = 0;
std::atomic<double> average_fitness = 0;
std::atomic<double> best_fitness = 0;
std::atomic<double> worst_fitness = 1;
tracked_vector<double> normalized_fitness{};
void clear()
{
overall_fitness = 0;
average_fitness = 0;
best_fitness = 0;
worst_fitness = 0;
normalized_fitness.clear();
}
friend bool operator==(const population_stats& a, const population_stats& b)
{
return a.overall_fitness.load(std::memory_order_relaxed) == b.overall_fitness.load(std::memory_order_relaxed) &&
a.average_fitness.load(std::memory_order_relaxed) == b.average_fitness.load(std::memory_order_relaxed) &&
a.best_fitness.load(std::memory_order_relaxed) == b.best_fitness.load(std::memory_order_relaxed) &&
a.worst_fitness.load(std::memory_order_relaxed) == b.worst_fitness.load(std::memory_order_relaxed) &&
a.normalized_fitness == b.normalized_fitness;
}
friend bool operator!=(const population_stats& a, const population_stats& b)
{
return !(a == b);
}
};
}
#endif //BLT_GP_UTIL_STATISTICS_H

View File

@ -316,14 +316,15 @@ namespace blt::gp
return secondary_value.load();
}
call_data_t start_measurement()
call_data_t start_measurement() const
{
return {primary_calls.load(), 0};
return {primary_calls.load(), secondary_value.load()};
}
void stop_measurement(call_data_t& data)
void stop_measurement(call_data_t& data) const
{
data.end_calls = primary_calls.load();
data.end_value = secondary_value.load();
}
private:

@ -1 +1 @@
Subproject commit 7198a8b0c32e35c9d80d8e44ff17c7199ddde6f8
Subproject commit f0fe0c1ceed644513eb8fa787aac3001a906d209

View File

@ -1,41 +0,0 @@
Performance counter stats for './cmake-build-release/blt-symbolic-regression-example' (30 runs):
81,986,993,284 branches ( +- 15.89% ) (19.93%)
194,632,894 branch-misses # 0.24% of all branches ( +- 21.10% ) (19.84%)
32,561,539 cache-misses # 0.89% of all cache refs ( +- 10.21% ) (19.95%)
3,645,509,810 cache-references ( +- 15.93% ) (20.11%)
169,957,442,648 cycles ( +- 15.85% ) (20.26%)
426,558,894,577 instructions # 2.51 insn per cycle ( +- 16.24% ) (20.29%)
0 alignment-faults
9,103 cgroup-switches ( +- 13.62% )
52,586 faults ( +- 5.74% )
1,823,320,688 ns duration_time ( +- 12.76% )
41,213,439,537 ns user_time ( +- 3.68% )
219,435,124 ns system_time ( +- 2.44% )
132,928,139,347 L1-dcache-loads ( +- 15.55% ) (20.40%)
2,559,138,346 L1-dcache-load-misses # 1.93% of all L1-dcache accesses ( +- 15.53% ) (20.37%)
852,474,938 L1-dcache-prefetches ( +- 19.61% ) (20.44%)
1,035,909,753 L1-icache-loads ( +- 11.73% ) (20.45%)
1,451,589 L1-icache-load-misses # 0.14% of all L1-icache accesses ( +- 13.61% ) (20.50%)
37,722,800 dTLB-loads ( +- 14.93% ) (20.52%)
4,119,243 dTLB-load-misses # 10.92% of all dTLB cache accesses ( +- 10.99% ) (20.55%)
1,318,136 iTLB-loads ( +- 20.32% ) (20.51%)
367,939 iTLB-load-misses # 27.91% of all iTLB cache accesses ( +- 12.34% ) (20.42%)
2,730,214,946 l2_request_g1.all_no_prefetch ( +- 15.32% ) (20.43%)
52,586 page-faults ( +- 5.74% )
52,583 page-faults:u ( +- 5.75% )
3 page-faults:k ( +- 3.96% )
132,786,226,560 L1-dcache-loads ( +- 15.54% ) (20.33%)
2,581,181,694 L1-dcache-load-misses # 1.94% of all L1-dcache accesses ( +- 15.34% ) (20.26%)
<not supported> LLC-loads
<not supported> LLC-load-misses
1,021,814,075 L1-icache-loads ( +- 11.67% ) (20.19%)
1,376,958 L1-icache-load-misses # 0.13% of all L1-icache accesses ( +- 13.76% ) (20.09%)
38,065,494 dTLB-loads ( +- 14.76% ) (20.09%)
4,174,010 dTLB-load-misses # 11.06% of all dTLB cache accesses ( +- 10.90% ) (20.14%)
1,407,386 iTLB-loads ( +- 20.45% ) (20.09%)
338,781 iTLB-load-misses # 25.70% of all iTLB cache accesses ( +- 12.61% ) (20.05%)
873,873,406 L1-dcache-prefetches ( +- 19.41% ) (20.00%)
<not supported> L1-dcache-prefetch-misses
1.823 +- 0.233 seconds time elapsed ( +- 12.76% )

View File

@ -17,7 +17,7 @@
*/
#include <blt/gp/generators.h>
#include <blt/gp/program.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <stack>
namespace blt::gp
@ -27,8 +27,8 @@ namespace blt::gp
struct stack
{
blt::gp::operator_id id;
blt::size_t depth;
operator_id id;
size_t depth;
};
inline std::stack<stack> get_initial_stack(gp_program& program, type_id root_type)
@ -49,10 +49,10 @@ namespace blt::gp
}
template<typename Func>
inline void create_tree(tree_t& tree, Func&& perChild, const generator_arguments& args)
void create_tree(tree_t& tree, Func&& perChild, const generator_arguments& args)
{
std::stack<stack> tree_generator = get_initial_stack(args.program, args.root_type);
blt::size_t max_depth = 0;
size_t max_depth = 0;
while (!tree_generator.empty())
{
@ -61,17 +61,15 @@ namespace blt::gp
auto& info = args.program.get_operator_info(top.id);
tree.get_operations().emplace_back(
tree.emplace_operator(
args.program.get_typesystem().get_type(info.return_type).size(),
top.id,
args.program.is_operator_ephemeral(top.id));
args.program.is_operator_ephemeral(top.id),
args.program.get_operator_flags(top.id));
max_depth = std::max(max_depth, top.depth);
if (args.program.is_operator_ephemeral(top.id))
{
info.func(nullptr, tree.get_values(), tree.get_values());
continue;
}
for (const auto& child : info.argument_types)
std::forward<Func>(perChild)(args.program, tree_generator, child, top.depth + 1);

View File

@ -17,6 +17,13 @@
*/
#include <blt/gp/program.h>
#include <iostream>
#include <blt/std/variant.h>
#ifndef BLT_ASSERT_RET
#define BLT_ASSERT_RET(expr) if (!(expr)) { return false; }
#endif
#define BLT_READ(read_statement, size) do { auto read = read_statement; if (read != size) { return blt::gp::errors::serialization::invalid_read_t{read, size}; } } while (false)
namespace blt::gp
{
@ -24,25 +31,28 @@ namespace blt::gp
// this is largely to not break the tests :3
// it's also to allow for quick setup of a gp program if you don't care how crossover or mutation is handled
static advanced_mutation_t s_mutator;
static crossover_t s_crossover;
// static subtree_crossover_t s_crossover;
static one_point_crossover_t s_crossover;
static ramped_half_initializer_t s_init;
prog_config_t::prog_config_t(): mutator(s_mutator), crossover(s_crossover), pop_initializer(s_init)
{
}
prog_config_t::prog_config_t(const std::reference_wrapper<population_initializer_t>& popInitializer):
mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer)
{}
{
}
prog_config_t::prog_config_t(size_t populationSize, const std::reference_wrapper<population_initializer_t>& popInitializer):
population_size(populationSize), mutator(s_mutator), crossover(s_crossover), pop_initializer(popInitializer)
{}
{
}
prog_config_t::prog_config_t(size_t populationSize):
population_size(populationSize), mutator(s_mutator), crossover(s_crossover), pop_initializer(s_init)
{}
{
}
random_t& gp_program::get_random() const
{
@ -56,6 +66,171 @@ namespace blt::gp
return allocator;
}
void gp_program::save_generation(fs::writer_t& writer)
{
const auto individuals = current_pop.get_individuals().size();
writer.write(&individuals, sizeof(individuals));
for (const auto& individual : current_pop.get_individuals())
{
writer.write(&individual.fitness, sizeof(individual.fitness));
individual.tree.to_file(writer);
}
}
bool gp_program::load_generation(fs::reader_t& reader)
{
size_t individuals;
BLT_ASSERT_RET(reader.read(&individuals, sizeof(individuals)) == sizeof(individuals));
if (current_pop.get_individuals().size() != individuals)
{
for (size_t i = current_pop.get_individuals().size(); i < individuals; i++)
current_pop.get_individuals().emplace_back(tree_t{*this});
}
for (auto& individual : current_pop.get_individuals())
{
BLT_ASSERT_RET(reader.read(&individual.fitness, sizeof(individual.fitness)) == sizeof(individual.fitness));
individual.tree.clear(*this);
individual.tree.from_file(reader);
}
return true;
}
void write_stat(fs::writer_t& writer, const population_stats& stat)
{
const auto overall_fitness = stat.overall_fitness.load();
const auto average_fitness = stat.average_fitness.load();
const auto best_fitness = stat.best_fitness.load();
const auto worst_fitness = stat.worst_fitness.load();
writer.write(&overall_fitness, sizeof(overall_fitness));
writer.write(&average_fitness, sizeof(average_fitness));
writer.write(&best_fitness, sizeof(best_fitness));
writer.write(&worst_fitness, sizeof(worst_fitness));
const size_t fitness_count = stat.normalized_fitness.size();
writer.write(&fitness_count, sizeof(fitness_count));
for (const auto& fitness : stat.normalized_fitness)
writer.write(&fitness, sizeof(fitness));
}
bool load_stat(fs::reader_t& reader, population_stats& stat)
{
BLT_ASSERT_RET(reader.read(&stat.overall_fitness, sizeof(stat.overall_fitness)) == sizeof(stat.overall_fitness));
BLT_ASSERT_RET(reader.read(&stat.average_fitness, sizeof(stat.average_fitness)) == sizeof(stat.average_fitness));
BLT_ASSERT_RET(reader.read(&stat.best_fitness, sizeof(stat.best_fitness)) == sizeof(stat.best_fitness));
BLT_ASSERT_RET(reader.read(&stat.worst_fitness, sizeof(stat.worst_fitness)) == sizeof(stat.worst_fitness));
size_t fitness_count;
BLT_ASSERT_RET(reader.read(&fitness_count, sizeof(fitness_count)) == sizeof(size_t));
stat.normalized_fitness.resize(fitness_count);
for (auto& fitness : stat.normalized_fitness)
BLT_ASSERT_RET(reader.read(&fitness, sizeof(fitness)) == sizeof(fitness));
return true;
}
void gp_program::save_state(fs::writer_t& writer)
{
const size_t operator_count = storage.operators.size();
writer.write(&operator_count, sizeof(operator_count));
for (const auto& [i, op] : enumerate(storage.operators))
{
writer.write(&i, sizeof(i));
bool has_name = storage.names[i].has_value();
writer.write(&has_name, sizeof(has_name));
if (has_name)
{
auto size = storage.names[i]->size();
writer.write(&size, sizeof(size));
writer.write(storage.names[i]->data(), size);
}
writer.write(&storage.operator_metadata[i].arg_size_bytes, sizeof(storage.operator_metadata[i].arg_size_bytes));
writer.write(&storage.operator_metadata[i].return_size_bytes, sizeof(storage.operator_metadata[i].return_size_bytes));
writer.write(&op.argc, sizeof(op.argc));
writer.write(&op.return_type, sizeof(op.return_type));
const size_t argc_type_count = op.argument_types.size();
writer.write(&argc_type_count, sizeof(argc_type_count));
for (const auto argument : op.argument_types)
writer.write(&argument, sizeof(argument));
}
const size_t history_count = statistic_history.size();
writer.write(&history_count, sizeof(history_count));
for (const auto& stat : statistic_history)
write_stat(writer, stat);
write_stat(writer, current_stats);
save_generation(writer);
}
std::optional<errors::serialization::serializer_error_t> gp_program::load_state(fs::reader_t& reader)
{
size_t operator_count;
BLT_READ(reader.read(&operator_count, sizeof(operator_count)), sizeof(operator_count));
if (operator_count != storage.operators.size())
return errors::serialization::unexpected_size_t{operator_count, storage.operators.size()};
for (size_t i = 0; i < operator_count; i++)
{
size_t expected_i;
BLT_READ(reader.read(&expected_i, sizeof(expected_i)), sizeof(expected_i));
if (expected_i != i)
return errors::serialization::invalid_operator_id_t{i, expected_i};
bool has_name;
BLT_READ(reader.read(&has_name, sizeof(has_name)), sizeof(has_name));
if (has_name)
{
size_t size;
BLT_READ(reader.read(&size, sizeof(size)), sizeof(size));
std::string name;
name.resize(size);
BLT_READ(reader.read(name.data(), size), static_cast<i64>(size));
if (!storage.names[i].has_value())
return errors::serialization::invalid_name_t{i, name, "NO NAME"};
if (name != *storage.names[i])
return errors::serialization::invalid_name_t{i, name, std::string{*storage.names[i]}};
const auto& op = storage.operators[i];
const auto& op_meta = storage.operator_metadata[i];
decltype(std::declval<decltype(storage.operator_metadata)::value_type>().arg_size_bytes) arg_size_bytes;
decltype(std::declval<decltype(storage.operator_metadata)::value_type>().return_size_bytes) return_size_bytes;
BLT_READ(reader.read(&arg_size_bytes, sizeof(arg_size_bytes)), sizeof(arg_size_bytes));
BLT_READ(reader.read(&return_size_bytes, sizeof(return_size_bytes)), sizeof(return_size_bytes));
if (op_meta.arg_size_bytes != arg_size_bytes)
return errors::serialization::mismatched_bytes_t{i, arg_size_bytes, op_meta.arg_size_bytes};
if (op_meta.return_size_bytes != return_size_bytes)
return errors::serialization::mismatched_bytes_t{i, return_size_bytes, op_meta.return_size_bytes};
argc_t argc;
BLT_READ(reader.read(&argc, sizeof(argc)), sizeof(argc));
if (argc.argc != op.argc.argc)
return errors::serialization::mismatched_argc_t{i, argc.argc, op.argc.argc};
if (argc.argc_context != op.argc.argc_context)
return errors::serialization::mismatched_argc_t{i, argc.argc_context, op.argc.argc_context};
type_id return_type;
BLT_READ(reader.read(&return_type, sizeof(return_type)), sizeof(return_type));
if (return_type != op.return_type)
return errors::serialization::mismatched_return_type_t{i, return_type, op.return_type};
size_t arg_type_count;
BLT_READ(reader.read(&arg_type_count, sizeof(arg_type_count)), sizeof(return_type));
if (arg_type_count != op.argument_types.size())
return errors::serialization::unexpected_size_t{arg_type_count, op.argument_types.size()};
for (size_t j = 0; j < arg_type_count; j++)
{
type_id type;
BLT_READ(reader.read(&type, sizeof(type)), sizeof(type));
if (type != op.argument_types[j])
return errors::serialization::mismatched_arg_type_t{i, j, type, op.argument_types[j]};
}
}
}
size_t history_count;
BLT_READ(reader.read(&history_count, sizeof(history_count)), sizeof(history_count));
statistic_history.resize(history_count);
for (size_t i = 0; i < history_count; i++)
load_stat(reader, statistic_history[i]);
load_stat(reader, current_stats);
load_generation(reader);
return {};
}
void gp_program::create_threads()
{
#ifdef BLT_TRACK_ALLOCATIONS
@ -67,7 +242,8 @@ namespace blt::gp
// main thread is thread0
for (blt::size_t i = 1; i < config.threads; i++)
{
thread_helper.threads.emplace_back(new std::thread([i, this]() {
thread_helper.threads.emplace_back(new std::thread([i, this]()
{
#ifdef BLT_TRACK_ALLOCATIONS
tracker.reserve();
tracker.await_thread_loading_complete(config.threads);

View File

@ -20,37 +20,34 @@
namespace blt::gp
{
void select_best_t::pre_process(gp_program&, population_t&)
{
// std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
// {
// return a.fitness.adjusted_fitness > b.fitness.adjusted_fitness;
// });
index = 0;
}
const tree_t& select_best_t::select(gp_program&, const population_t& pop)
{
auto& first = pop.get_individuals()[0];
double best_fitness = first.fitness.adjusted_fitness;
const tree_t* tree = &first.tree;
for (auto& ind : pop.get_individuals())
{
if (ind.fitness.adjusted_fitness > best_fitness)
{
best_fitness = ind.fitness.adjusted_fitness;
tree = &ind.tree;
const auto size = pop.get_individuals().size();
return pop.get_individuals()[index.fetch_add(1, std::memory_order_relaxed) % size].tree;
}
}
return *tree;
void select_worst_t::pre_process(gp_program&, population_t&)
{
// std::sort(pop.begin(), pop.end(), [](const auto& a, const auto& b)
// {
// return a.fitness.adjusted_fitness < b.fitness.adjusted_fitness;
// });
index = 0;
}
const tree_t& select_worst_t::select(gp_program&, const population_t& pop)
{
auto& first = pop.get_individuals()[0];
double worst_fitness = first.fitness.adjusted_fitness;
const tree_t* tree = &first.tree;
for (auto& ind : pop.get_individuals())
{
if (ind.fitness.adjusted_fitness < worst_fitness)
{
worst_fitness = ind.fitness.adjusted_fitness;
tree = &ind.tree;
}
}
return *tree;
const auto size = pop.get_individuals().size();
return pop.get_individuals()[(size - 1) - (index.fetch_add(1, std::memory_order_relaxed) % size)].tree;
}
const tree_t& select_random_t::select(gp_program& program, const population_t& pop)
@ -60,11 +57,20 @@ namespace blt::gp
const tree_t& select_tournament_t::select(gp_program& program, const population_t& pop)
{
blt::u64 best = program.get_random().get_u64(0, pop.get_individuals().size());
thread_local hashset_t<u64> already_selected;
already_selected.clear();
auto& i_ref = pop.get_individuals();
for (blt::size_t i = 0; i < selection_size; i++)
u64 best = program.get_random().get_u64(0, pop.get_individuals().size());
for (size_t i = 0; i < std::min(selection_size, pop.get_individuals().size()); i++)
{
auto sel_point = program.get_random().get_u64(0ul, pop.get_individuals().size());
u64 sel_point;
do
{
sel_point = program.get_random().get_u64(0ul, pop.get_individuals().size());;
}
while (already_selected.contains(sel_point));
already_selected.insert(sel_point);
if (i_ref[sel_point].fitness.adjusted_fitness > i_ref[best].fitness.adjusted_fitness)
best = sel_point;
}
@ -81,7 +87,8 @@ namespace blt::gp
{
if (choice <= stats.normalized_fitness[index])
return ref.tree;
} else
}
else
{
if (choice > stats.normalized_fitness[index - 1] && choice <= stats.normalized_fitness[index])
return ref.tree;

106
src/sync.cpp Normal file
View File

@ -0,0 +1,106 @@
/*
* <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 <blt/gp/sync.h>
#include <thread>
#include <atomic>
#include <blt/gp/program.h>
#include <blt/std/time.h>
namespace blt::gp
{
struct global_sync_state_t
{
std::vector<sync_t*> syncs;
std::mutex mutex;
std::thread* thread = nullptr;
std::atomic_bool should_run = true;
std::condition_variable condition_variable;
void add(sync_t* sync)
{
if (thread == nullptr)
{
thread = new std::thread([this]()
{
while (should_run)
{
std::unique_lock lock(mutex);
condition_variable.wait_for(lock, std::chrono::milliseconds(100));
const auto current_time = system::getCurrentTimeMilliseconds();
for (const auto& sync : syncs)
sync->trigger(current_time);
}
});
}
std::scoped_lock lock(mutex);
syncs.push_back(sync);
}
void remove(const sync_t* sync)
{
if (thread == nullptr)
{
BLT_WARN("Tried to remove sync from global sync state, but no thread was running");
return;
}
std::unique_lock lock(mutex);
const auto iter = std::find(syncs.begin(), syncs.end(), sync);
std::iter_swap(iter, syncs.end() - 1);
syncs.pop_back();
if (syncs.empty())
{
lock.unlock();
should_run = false;
condition_variable.notify_all();
thread->join();
delete thread;
thread = nullptr;
}
}
};
global_sync_state_t& get_state()
{
static global_sync_state_t state;
return state;
}
sync_t::sync_t(gp_program& program, fs::writer_t& writer): m_program(&program), m_writer(&writer)
{
get_state().add(this);
}
void sync_t::trigger(const u64 current_time) const
{
if ((m_timer_seconds && (current_time % *m_timer_seconds == 0)) || (m_generations && (m_program->get_current_generation() % *m_generations ==
0)))
{
if (m_reset_to_start_of_file)
m_writer->seek(0, fs::writer_t::seek_origin::seek_set);
if (m_whole_program)
m_program->save_state(*m_writer);
else
m_program->save_generation(*m_writer);
}
}
sync_t::~sync_t()
{
get_state().remove(this);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -18,23 +18,19 @@
#include <blt/gp/tree.h>
#include <blt/gp/stack.h>
#include <blt/std/assert.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <blt/gp/program.h>
#include <stack>
namespace blt::gp
{
// this one will copy previous bytes over
template<typename T>
blt::span<blt::u8> get_pointer_for_size(blt::size_t size)
template <typename>
static u8* get_thread_pointer_for_size(const size_t bytes)
{
static blt::span<blt::u8> buffer{nullptr, 0};
if (buffer.size() < size)
{
delete[] buffer.data();
buffer = {new blt::u8[size], size};
}
return buffer;
thread_local expanding_buffer<u8> buffer;
if (bytes > buffer.size())
buffer.resize(bytes);
return buffer.data();
}
std::ostream& create_indent(std::ostream& out, blt::size_t amount, bool pretty_print)
@ -58,7 +54,16 @@ namespace blt::gp
return "(" + std::string(program.get_typesystem().get_type(id).name()) + ")";
}
void tree_t::print(gp_program& program, std::ostream& out, bool print_literals, bool pretty_print, bool include_types) const
void tree_t::byte_only_transaction_t::move(const size_t bytes_to_move)
{
bytes = bytes_to_move;
data = get_thread_pointer_for_size<struct move_tempoary_bytes>(bytes);
tree.values.copy_to(data, bytes);
tree.values.pop_bytes(bytes);
}
void tree_t::print(std::ostream& out, const bool print_literals, const bool pretty_print, const bool include_types,
const ptrdiff_t marked_index) const
{
std::stack<blt::size_t> arguments_left;
blt::size_t indent = 0;
@ -72,34 +77,41 @@ 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)
for (const auto& [i, v] : enumerate(operations))
{
auto info = program.get_operator_info(v.id);
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);
auto info = m_program->get_operator_info(v.id());
const auto name = m_program->get_name(v.id()) ? m_program->get_name(v.id()).value() : "NULL";
auto return_type = get_return_type(*m_program, info.return_type, include_types);
if (static_cast<ptrdiff_t>(i) == marked_index)
{
out << "[ERROR OCCURRED HERE] -> ";
}
if (info.argc.argc > 0)
{
create_indent(out, indent, pretty_print) << "(";
indent++;
arguments_left.emplace(info.argc.argc);
out << name << return_type << end_indent(pretty_print);
} else
}
else
{
if (print_literals)
{
create_indent(out, indent, pretty_print);
if (program.is_operator_ephemeral(v.id))
if (m_program->is_operator_ephemeral(v.id()))
{
program.get_print_func(v.id)(out, reversed);
reversed.pop_bytes(stack_allocator::aligned_size(v.type_size));
} else
m_program->get_print_func(v.id())(out, reversed);
reversed.pop_bytes(v.type_size());
}
else
out << name;
out << return_type << end_indent(pretty_print);
} else
}
else
create_indent(out, indent, pretty_print) << name << return_type << end_indent(pretty_print);
}
@ -112,7 +124,8 @@ namespace blt::gp
indent--;
create_indent(out, indent, pretty_print) << ")" << end_indent(pretty_print);
continue;
} else
}
else
{
if (!pretty_print)
out << " ";
@ -130,7 +143,8 @@ namespace blt::gp
indent--;
create_indent(out, indent, pretty_print) << ")" << end_indent(pretty_print);
continue;
} else
}
else
{
BLT_ERROR("Failed to print tree correctly!");
break;
@ -140,17 +154,20 @@ namespace blt::gp
out << '\n';
}
blt::size_t tree_t::get_depth(gp_program& program)
size_t tree_t::get_depth(gp_program& program) const
{
blt::size_t depth = 0;
size_t depth = 0;
auto operations_stack = operations;
tracked_vector<blt::size_t> values_process;
tracked_vector<blt::size_t> value_stack;
thread_local tracked_vector<size_t> values_process;
thread_local tracked_vector<size_t> value_stack;
values_process.clear();
value_stack.clear();
for (const auto& op : operations_stack)
{
if (op.is_value)
if (op.is_value())
value_stack.push_back(1);
}
@ -159,7 +176,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);
@ -167,145 +184,632 @@ namespace blt::gp
value_stack.pop_back();
continue;
}
blt::size_t local_depth = 0;
for (blt::size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
size_t local_depth = 0;
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, program.get_operator_flags(operation.id()));
}
return depth;
}
blt::ptrdiff_t tree_t::find_endpoint(gp_program& program, blt::ptrdiff_t index) const
tree_t::subtree_point_t tree_t::select_subtree(const double terminal_chance) const
{
blt::i64 children_left = 0;
do
{
const auto point = m_program->get_random().get_u64(0, operations.size());
const auto& info = m_program->get_operator_info(operations[point].id());
if (!info.argc.is_terminal())
return {static_cast<ptrdiff_t>(point), info.return_type};
if (m_program->get_random().choice(terminal_chance))
return {static_cast<ptrdiff_t>(point), info.return_type};
}
while (true);
}
std::optional<tree_t::subtree_point_t> tree_t::select_subtree(const type_id type, const u32 max_tries, const double terminal_chance) const
{
for (u32 i = 0; i < max_tries; ++i)
{
if (const auto tree = select_subtree(terminal_chance); tree.type == type)
return tree;
}
return {};
}
tree_t::subtree_point_t tree_t::select_subtree_traverse(const double terminal_chance, const double depth_multiplier) const
{
size_t index = 0;
double depth = 0;
double exit_chance = 0;
while (true)
{
const auto& info = m_program->get_operator_info(operations[index].id());
if (info.argc.is_terminal())
{
if (m_program->get_random().choice(terminal_chance))
return {static_cast<ptrdiff_t>(index), info.return_type};
index = 0;
depth = 0;
exit_chance = 0;
continue;
}
if (m_program->get_random().choice(exit_chance))
return {static_cast<ptrdiff_t>(index), info.return_type};
const auto child = m_program->get_random().get_u32(0, info.argc.argc);
index++;
for (u32 i = 0; i < child; i++)
index = find_endpoint(static_cast<ptrdiff_t>(index));
++depth;
exit_chance = 1.0 - (1.0 / (1 + depth * depth_multiplier * 0.5));
}
}
std::optional<tree_t::subtree_point_t> tree_t::select_subtree_traverse(const type_id type, const u32 max_tries, const double terminal_chance,
const double depth_multiplier) const
{
for (u32 i = 0; i < max_tries; ++i)
{
if (const auto tree = select_subtree_traverse(terminal_chance, depth_multiplier); tree.type == type)
return tree;
}
return {};
}
void tree_t::copy_subtree(const subtree_point_t point, const ptrdiff_t extent, tracked_vector<op_container_t>& operators, stack_allocator& stack)
{
const auto point_begin_itr = operations.begin() + point.pos;
const auto point_end_itr = operations.begin() + extent;
const size_t after_bytes = accumulate_type_sizes(point_end_itr, operations.end());
const size_t ops = std::distance(point_begin_itr, point_end_itr);
operators.reserve(operators.size() + ops);
// TODO something better!
for (size_t i = 0; i < ops; ++i)
operators.emplace_back(0, 0, false, operator_special_flags{});
size_t for_bytes = 0;
size_t pos = 0;
for (auto& it : iterate(point_begin_itr, point_end_itr).rev())
{
if (it.is_value())
{
for_bytes += it.type_size();
if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
{
auto [_, ptr] = values.access_pointer(for_bytes + after_bytes, it.type_size());
++*ptr;
}
}
operators[operators.size() - 1 - (pos++)] = it;
}
stack.copy_from(values, for_bytes, after_bytes);
}
void tree_t::swap_subtrees(const child_t our_subtree, tree_t& other_tree, const child_t other_subtree)
{
const auto c1_subtree_begin_itr = operations.begin() + our_subtree.start;
const auto c1_subtree_end_itr = operations.begin() + our_subtree.end;
const auto c2_subtree_begin_itr = other_tree.operations.begin() + other_subtree.start;
const auto c2_subtree_end_itr = other_tree.operations.begin() + other_subtree.end;
thread_local tracked_vector<op_container_t> c1_subtree_operators;
thread_local tracked_vector<op_container_t> c2_subtree_operators;
c1_subtree_operators.clear();
c2_subtree_operators.clear();
c1_subtree_operators.reserve(std::distance(c1_subtree_begin_itr, c1_subtree_end_itr));
c2_subtree_operators.reserve(std::distance(c2_subtree_begin_itr, c2_subtree_end_itr));
// i don't think this is required for swapping values, since the total number of additions is net zero
// the tree isn't destroyed at any point.
size_t c1_subtree_bytes = 0;
for (const auto& it : iterate(c1_subtree_begin_itr, c1_subtree_end_itr))
{
if (it.is_value())
{
// if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
// {
// auto& ptr = values.access_pointer_forward(for_our_bytes, it.type_size());
// ++*ptr;
// }
c1_subtree_bytes += it.type_size();
}
c1_subtree_operators.push_back(it);
}
size_t c2_subtree_bytes = 0;
for (const auto& it : iterate(c2_subtree_begin_itr, c2_subtree_end_itr))
{
if (it.is_value())
{
// if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
// {
// auto& ptr = values.access_pointer_forward(for_other_bytes, it.type_size());
// ++*ptr;
// }
c2_subtree_bytes += it.type_size();
}
c2_subtree_operators.push_back(it);
}
const size_t c1_stack_after_bytes = accumulate_type_sizes(c1_subtree_end_itr, operations.end());
const size_t c2_stack_after_bytes = accumulate_type_sizes(c2_subtree_end_itr, other_tree.operations.end());
const auto c1_total = static_cast<ptrdiff_t>(c1_stack_after_bytes + c1_subtree_bytes);
const auto c2_total = static_cast<ptrdiff_t>(c2_stack_after_bytes + c2_subtree_bytes);
const auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
const auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
values.reserve(values.stored() - c1_subtree_bytes + c2_subtree_bytes);
other_tree.values.reserve(other_tree.values.stored() - c2_subtree_bytes + c1_subtree_bytes);
values.copy_to(copy_ptr_c1, c1_total);
values.pop_bytes(c1_total);
other_tree.values.copy_to(copy_ptr_c2, c2_total);
other_tree.values.pop_bytes(c2_total);
other_tree.values.copy_from(copy_ptr_c1, c1_subtree_bytes);
other_tree.values.copy_from(copy_ptr_c2 + c2_subtree_bytes, c2_stack_after_bytes);
values.copy_from(copy_ptr_c2, c2_subtree_bytes);
values.copy_from(copy_ptr_c1 + c1_subtree_bytes, c1_stack_after_bytes);
// now swap the operators
// auto insert_point_c1 = c1_subtree_begin_itr - 1;
// auto insert_point_c2 = c2_subtree_begin_itr - 1;
// invalidates [begin, end()) so the insert points should be fine
auto insert_point_c1 = operations.erase(c1_subtree_begin_itr, c1_subtree_end_itr);
auto insert_point_c2 = other_tree.operations.erase(c2_subtree_begin_itr, c2_subtree_end_itr);
operations.insert(insert_point_c1, c2_subtree_operators.begin(), c2_subtree_operators.end());
other_tree.operations.insert(insert_point_c2, c1_subtree_operators.begin(), c1_subtree_operators.end());
}
void tree_t::swap_subtrees(const subtree_point_t our_subtree, tree_t& other_tree, const subtree_point_t other_subtree)
{
swap_subtrees(child_t{our_subtree.pos, find_endpoint(our_subtree.pos)}, other_tree,
child_t{other_subtree.pos, find_endpoint(other_subtree.pos)});
}
void tree_t::replace_subtree(const subtree_point_t point, const ptrdiff_t extent, tree_t& other_tree)
{
const auto point_begin_itr = operations.begin() + point.pos;
const auto point_end_itr = operations.begin() + extent;
const size_t after_bytes = accumulate_type_sizes(point_end_itr, operations.end());
size_t for_bytes = 0;
for (auto& it : iterate(point_begin_itr, point_end_itr).rev())
{
if (it.is_value())
{
for_bytes += it.type_size();
if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
{
auto [val, ptr] = values.access_pointer(for_bytes + after_bytes, it.type_size());
--*ptr;
if (*ptr == 0)
handle_ptr_empty(ptr, val, it.id());
}
}
}
auto insert = operations.erase(point_begin_itr, point_end_itr);
const auto ptr = get_thread_pointer_for_size<struct replace>(after_bytes);
values.copy_to(ptr, after_bytes);
values.pop_bytes(after_bytes + for_bytes);
size_t copy_bytes = 0;
for (const auto& v : other_tree.operations)
{
if (v.is_value())
{
if (v.get_flags().is_ephemeral() && v.has_ephemeral_drop())
{
auto [_, pointer] = other_tree.values.access_pointer_forward(copy_bytes, v.type_size());
++*pointer;
}
copy_bytes += v.type_size();
}
insert = ++operations.emplace(insert, v);
}
values.insert(other_tree.values);
values.copy_from(ptr, after_bytes);
}
void tree_t::delete_subtree(const subtree_point_t point, const ptrdiff_t extent)
{
const auto point_begin_itr = operations.begin() + point.pos;
const auto point_end_itr = operations.begin() + extent;
const size_t after_bytes = accumulate_type_sizes(point_end_itr, operations.end());
size_t for_bytes = 0;
for (auto& it : iterate(point_begin_itr, point_end_itr).rev())
{
if (it.is_value())
{
for_bytes += it.type_size();
if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
{
auto [val, ptr] = values.access_pointer(for_bytes + after_bytes, it.type_size());
--*ptr;
if (*ptr == 0)
handle_ptr_empty(ptr, val, it.id());
}
}
}
operations.erase(point_begin_itr, point_end_itr);
const auto ptr = get_thread_pointer_for_size<struct replace>(after_bytes);
values.copy_to(ptr, after_bytes);
values.pop_bytes(after_bytes + for_bytes);
values.copy_from(ptr, after_bytes);
}
ptrdiff_t tree_t::insert_subtree(const subtree_point_t point, tree_t& other_tree)
{
const size_t after_bytes = accumulate_type_sizes(operations.begin() + point.pos, operations.end());
byte_only_transaction_t transaction{*this, after_bytes};
auto insert = operations.begin() + point.pos;
size_t bytes = 0;
for (auto& it : iterate(other_tree.operations).rev())
{
if (it.is_value())
{
bytes += it.type_size();
if (it.get_flags().is_ephemeral() && it.has_ephemeral_drop())
{
auto [_, ptr] = other_tree.values.access_pointer(bytes, it.type_size());
++*ptr;
}
}
insert = operations.insert(insert, it);
}
values.insert(other_tree.values);
return static_cast<ptrdiff_t>(point.pos + other_tree.size());
}
ptrdiff_t tree_t::find_endpoint(ptrdiff_t start) const
{
i64 children_left = 0;
do
{
const auto& type = program.get_operator_info(operations[index].id);
const auto& type = m_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++;
} while (children_left > 0);
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
tree_t& tree_t::get_thread_local(gp_program& program)
{
blt::i64 children_left = 0;
do
{
if (index == 0)
return 0;
const auto& type = program.get_operator_info(operations[index].id);
if (type.argc.argc > 0)
children_left -= type.argc.argc;
children_left++;
if (children_left <= 0)
break;
--index;
} while (true);
return index;
thread_local tree_t tree{program};
tree.clear(program);
return tree;
}
bool tree_t::check(gp_program& program, void* context) const
void tree_t::handle_operator_inserted(const op_container_t& op)
{
blt::size_t bytes_expected = 0;
auto bytes_size = values.size().total_used_bytes;
if (m_program->is_operator_ephemeral(op.id()))
{
// Ephemeral values have corresponding insertions into the stack
m_program->get_operator_info(op.id()).func(nullptr, values, values);
if (m_program->operator_has_ephemeral_drop(op.id()))
{
auto [_, ptr] = values.access_pointer(op.type_size(), op.type_size());
ptr = new std::atomic_uint64_t(1);
ptr.bit(0, true);
}
}
}
for (const auto& op : get_operations())
void tree_t::handle_ptr_empty(const mem::pointer_storage<std::atomic_uint64_t>& ptr, u8* data, const operator_id id) const
{
if (op.is_value)
bytes_expected += stack_allocator::aligned_size(op.type_size);
m_program->get_destroy_func(id)(detail::destroy_t::RETURN, data);
delete ptr.get();
// BLT_INFO("Deleting pointer!");
}
evaluation_context& tree_t::evaluate(void* ptr) const
{
return m_program->get_eval_func()(*this, ptr);
}
bool tree_t::check(void* context) const
{
size_t bytes_expected = 0;
const auto bytes_size = values.stored();
for (const auto& op : operations)
{
if (op.is_value())
bytes_expected += op.type_size();
}
if (bytes_expected != bytes_size)
{
BLT_WARN_STREAM << "Stack state: " << values.size() << "\n";
BLT_WARN("Child tree bytes %ld vs expected %ld, difference: %ld", bytes_size, bytes_expected,
static_cast<blt::ptrdiff_t>(bytes_expected) - static_cast<blt::ptrdiff_t>(bytes_size));
BLT_WARN("Amount of bytes in stack doesn't match the number of bytes expected for the operations");
BLT_ERROR("Stack state: Stored: {}; Capacity: {}; Remainder: {}", values.stored(), values.capacity(), values.remainder());
BLT_ERROR("Child tree bytes {} vs expected {}, difference: {}", bytes_size, bytes_expected,
static_cast<ptrdiff_t>(bytes_expected) - static_cast<ptrdiff_t>(bytes_size));
BLT_ERROR("Amount of bytes in stack doesn't match the number of bytes expected for the operations");
return false;
}
size_t total_produced = 0;
size_t total_consumed = 0;
size_t index = 0;
try
{
// copy the initial values
evaluation_context results{};
auto value_stack = values;
auto& values_process = results.values;
blt::size_t total_produced = 0;
blt::size_t total_consumed = 0;
for (const auto& operation : blt::reverse_iterate(operations.begin(), operations.end()))
for (const auto& operation : iterate(operations).rev())
{
if (operation.is_value)
++index;
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 = m_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 += m_program->get_typesystem().get_type(arg).size();
m_program->get_operator_info(operation.id()).func(context, values_process, values_process);
total_produced += m_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 = static_cast<ptrdiff_t>(results.values.stored());
const auto v2 = static_cast<ptrdiff_t>(operations.front().type_size());
// ephemeral don't need to be dropped as there are no copies which matter when checking the tree
if (!operations.front().get_flags().is_ephemeral())
m_program->get_destroy_func(operations.front().id())(detail::destroy_t::RETURN, results.values.from(operations.front().type_size()));
if (v1 != v2)
{
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,
const auto vd = std::abs(v1 - v2);
BLT_ERROR("found {} bytes expected {} bytes, total difference: {}", v1, v2, vd);
BLT_ERROR("Total Produced {} || Total Consumed {} || Total Difference {}", total_produced, total_consumed,
std::abs(static_cast<blt::ptrdiff_t>(total_produced) - static_cast<blt::ptrdiff_t>(total_consumed)));
return false;
}
}
catch (std::exception& e)
{
BLT_ERROR("Exception occurred \"{}\"", e.what());
BLT_ERROR("Total Produced {} || Total Consumed {} || Total Difference {}", total_produced, total_consumed,
std::abs(static_cast<blt::ptrdiff_t>(total_produced) - static_cast<blt::ptrdiff_t>(total_consumed)));
BLT_ERROR("We failed at index {}", index);
return false;
}
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(tracked_vector<child_t>& vec, const size_t parent_node, const size_t argc) const
{
BLT_ASSERT_MSG(vec.empty(), "Vector to find_child_extends should be empty!");
while (vec.size() < argc)
{
auto current_point = vec.size();
child_t prev{};
const auto current_point = vec.size();
child_t prev; // NOLINT
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(static_cast<ptrdiff_t>(parent_node + 1))
};
vec.push_back(prev);
continue;
} else
}
prev = vec[current_point - 1];
child_t next = {prev.end, find_endpoint(program, prev.end)};
child_t next = {prev.end, find_endpoint(prev.end)};
vec.push_back(next);
}
}
tree_t::tree_t(gp_program& program): func(&program.get_eval_func())
{
}
void tree_t::clear(gp_program& program)
{
auto* f = &program.get_eval_func();
if (f != func)
func = f;
auto* f = &program;
if (&program != m_program)
m_program = f;
size_t total_bytes = 0;
for (const auto& op : iterate(operations))
{
if (op.is_value())
{
if (op.get_flags().is_ephemeral() && op.has_ephemeral_drop())
{
auto [val, ptr] = values.access_pointer_forward(total_bytes, op.type_size());
--*ptr;
if (*ptr == 0)
handle_ptr_empty(ptr, val, op.id());
}
total_bytes += op.type_size();
}
}
operations.clear();
values.reset();
}
void tree_t::insert_operator(const size_t index, const op_container_t& container)
{
if (container.get_flags().is_ephemeral())
{
byte_only_transaction_t move{*this, total_value_bytes(index)};
handle_operator_inserted(container);
}
operations.insert(operations.begin() + static_cast<ptrdiff_t>(index), container);
}
tree_t::subtree_point_t tree_t::subtree_from_point(ptrdiff_t point) const
{
return {point, m_program->get_operator_info(operations[point].id()).return_type};
}
size_t tree_t::required_size() const
{
// 2 size_t used to store expected_length of operations + size of the values stack
return 2 * sizeof(size_t) + operations.size() * sizeof(size_t) + values.stored();
}
void tree_t::to_byte_array(std::byte* out) const
{
const auto op_size = operations.size();
std::memcpy(out, &op_size, sizeof(size_t));
out += sizeof(size_t);
for (const auto& op : operations)
{
constexpr auto size_of_op = sizeof(operator_id);
auto id = op.id();
std::memcpy(out, &id, size_of_op);
out += size_of_op;
}
const auto val_size = values.stored();
std::memcpy(out, &val_size, sizeof(size_t));
out += sizeof(size_t);
std::memcpy(out, values.data(), val_size);
}
void tree_t::to_file(fs::writer_t& file) const
{
const auto op_size = operations.size();
BLT_ASSERT(file.write(&op_size, sizeof(size_t)) == sizeof(size_t));
for (const auto& op : operations)
{
auto id = op.id();
file.write(&id, sizeof(operator_id));
}
const auto val_size = values.stored();
BLT_ASSERT(file.write(&val_size, sizeof(size_t)) == sizeof(size_t));
BLT_ASSERT(file.write(values.data(), val_size) == static_cast<i64>(val_size));
}
void tree_t::from_byte_array(const std::byte* in)
{
size_t ops_to_read;
std::memcpy(&ops_to_read, in, sizeof(size_t));
in += sizeof(size_t);
operations.reserve(ops_to_read);
for (size_t i = 0; i < ops_to_read; i++)
{
operator_id id;
std::memcpy(&id, in, sizeof(operator_id));
in += sizeof(operator_id);
operations.emplace_back(
m_program->get_typesystem().get_type(m_program->get_operator_info(id).return_type).size(),
id,
m_program->is_operator_ephemeral(id),
m_program->get_operator_flags(id)
);
}
size_t val_size;
std::memcpy(&val_size, in, sizeof(size_t));
in += sizeof(size_t);
// TODO replace instances of u8 that are used to alias types with the proper std::byte
values.copy_from(reinterpret_cast<const u8*>(in), val_size);
}
void tree_t::from_file(fs::reader_t& file)
{
size_t ops_to_read;
BLT_ASSERT(file.read(&ops_to_read, sizeof(size_t)) == sizeof(size_t));
operations.reserve(ops_to_read);
for (size_t i = 0; i < ops_to_read; i++)
{
operator_id id;
BLT_ASSERT(file.read(&id, sizeof(operator_id)) == sizeof(operator_id));
operations.emplace_back(
m_program->get_typesystem().get_type(m_program->get_operator_info(id).return_type).size(),
id,
m_program->is_operator_ephemeral(id),
m_program->get_operator_flags(id)
);
}
size_t bytes_in_head;
BLT_ASSERT(file.read(&bytes_in_head, sizeof(size_t)) == sizeof(size_t));
values.resize(bytes_in_head);
BLT_ASSERT(file.read(values.data(), bytes_in_head) == static_cast<i64>(bytes_in_head));
}
void tree_t::modify_operator(const size_t point, operator_id new_id, std::optional<type_id> return_type)
{
if (!return_type)
return_type = m_program->get_operator_info(new_id).return_type;
byte_only_transaction_t move_data{*this};
if (operations[point].is_value())
{
const size_t after_bytes = accumulate_type_sizes(operations.begin() + static_cast<ptrdiff_t>(point) + 1, operations.end());
move_data.move(after_bytes);
if (operations[point].get_flags().is_ephemeral() && operations[point].has_ephemeral_drop())
{
auto [val, ptr] = values.access_pointer(operations[point].type_size(), operations[point].type_size());
--*ptr;
if (*ptr == 0)
handle_ptr_empty(ptr, val, operations[point].id());
}
values.pop_bytes(operations[point].type_size());
}
operations[point] = {
m_program->get_typesystem().get_type(*return_type).size(),
new_id,
m_program->is_operator_ephemeral(new_id),
m_program->get_operator_flags(new_id)
};
if (operations[point].get_flags().is_ephemeral())
{
if (move_data.empty())
{
const size_t after_bytes = accumulate_type_sizes(operations.begin() + static_cast<ptrdiff_t>(point) + 1, operations.end());
move_data.move(after_bytes);
}
handle_operator_inserted(operations[point]);
}
}
bool operator==(const tree_t& a, const tree_t& b)
{
if (a.operations.size() != b.operations.size())
return false;
if (a.values.stored() != b.values.stored())
return false;
return std::equal(a.operations.begin(), a.operations.end(), b.operations.begin());
}
bool operator==(const op_container_t& a, const op_container_t& b)
{
return a.id() == b.id();
}
bool operator==(const individual_t& a, const individual_t& b)
{
return a.tree == b.tree;
}
}

50
src/util/statistics.cpp Normal file
View File

@ -0,0 +1,50 @@
/*
* <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 <blt/gp/util/statistics.h>
#include <blt/format/format.h>
#include <numeric>
namespace blt::gp {
std::string confusion_matrix_t::pretty_print(const std::string& table_name) const
{
string::TableFormatter formatter{table_name};
formatter.addColumn("Predicted " + name_A);
formatter.addColumn("Predicted " + name_B);
formatter.addColumn("Actual Class");
string::TableRow row;
row.rowValues.push_back(std::to_string(is_A_pred_A));
row.rowValues.push_back(std::to_string(is_A_pred_B));
row.rowValues.push_back(name_A);
formatter.addRow(row);
string::TableRow row2;
row2.rowValues.push_back(std::to_string(is_B_pred_A));
row2.rowValues.push_back(std::to_string(is_B_pred_B));
row2.rowValues.push_back(name_B);
formatter.addRow(row2);
auto tbl = formatter.createTable(true, true);
return std::accumulate(tbl.begin(), tbl.end(), std::string{}, [](const std::string& a, const std::string& b)
{
return a + "\n" + b;
});
}
}

View File

@ -15,9 +15,10 @@
* 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 <blt/gp/stats.h>
#include <blt/std/logging.h>
#include "blt/std/format.h"
#include <blt/gp/util/trackers.h>
#include <blt/logging/logging.h>
#include "blt/format/format.h"
#include <numeric>
namespace blt::gp
{

194
tests/2_type_drop_test.cpp Normal file
View File

@ -0,0 +1,194 @@
/*
* <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 <blt/gp/program.h>
#include <blt/logging/logging.h>
using namespace blt::gp;
std::atomic_uint64_t normal_construct = 0;
std::atomic_uint64_t ephemeral_construct = 0;
std::atomic_uint64_t normal_drop = 0;
std::atomic_uint64_t ephemeral_drop = 0;
std::atomic_uint64_t max_allocated = 0;
struct drop_type
{
float* m_value;
bool ephemeral = false;
drop_type() : m_value(new float(0))
{
++normal_construct;
}
explicit drop_type(const float silly) : m_value(new float(silly))
{
++normal_construct;
}
explicit drop_type(const float silly, bool) : m_value(new float(silly)), ephemeral(true)
{
// BLT_TRACE("Constructor with value %f", silly);
++ephemeral_construct;
}
[[nodiscard]] float value() const
{
return *m_value;
}
void drop() const
{
if (ephemeral)
{
std::cout << ("Ephemeral drop") << std::endl;
++ephemeral_drop;
}
else
++normal_drop;
delete m_value;
}
friend std::ostream& operator<<(std::ostream& os, const drop_type& dt)
{
os << dt.m_value;
return os;
}
};
struct context
{
float x, y;
};
prog_config_t config = prog_config_t()
.set_initial_min_tree_size(2)
.set_initial_max_tree_size(6)
.set_elite_count(2)
.set_crossover_chance(0.8)
.set_mutation_chance(0.1)
.set_reproduction_chance(0.1)
.set_max_generations(50)
.set_pop_size(500)
.set_thread_count(1);
example::symbolic_regression_t regression{691ul, config};
operation_t add{[](const drop_type a, const drop_type b) { return drop_type{a.value() + b.value()}; }, "add"};
operation_t addf{[](const float a, const float b) { return a + b; }, "addf"};
operation_t sub([](const drop_type a, const drop_type b) { return drop_type{a.value() - b.value()}; }, "sub");
operation_t subf([](const float a, const float b) { return a - b; }, "subf");
operation_t mul([](const drop_type a, const drop_type b) { return drop_type{a.value() * b.value()}; }, "mul");
operation_t mulf([](const float a, const float b) { return a * b; }, "mulf");
operation_t pro_div([](const drop_type a, const drop_type b) { return drop_type{b.value() == 0.0f ? 0.0f : a.value() / b.value()}; }, "div");
operation_t pro_divf([](const float a, const float b) { return b == 0.0f ? 0.0f : a / b; }, "divf");
operation_t op_sin([](const drop_type a) { return drop_type{std::sin(a.value())}; }, "sin");
operation_t op_sinf([](const float a) { return std::sin(a); }, "sinf");
operation_t op_cos([](const drop_type a) { return drop_type{std::cos(a.value())}; }, "cos");
operation_t op_cosf([](const float a) { return std::cos(a); }, "cosf");
operation_t op_exp([](const drop_type a) { return drop_type{std::exp(a.value())}; }, "exp");
operation_t op_expf([](const float a) { return std::exp(a); }, "expf");
operation_t op_log([](const drop_type a) { return drop_type{a.value() <= 0.0f ? 0.0f : std::log(a.value())}; }, "log");
operation_t op_logf([](const float a) { return a <= 0.0f ? 0.0f : std::log(a); }, "logf");
operation_t op_tof([](const drop_type a) { return a.value(); }, "to_f");
operation_t op_todrop([](const float a) { return drop_type{a}; }, "to_drop");
operation_t op_mixed_input([](const drop_type a, const float f)
{
return a.value() + f;
}, "mixed_input");
auto lit = operation_t([]()
{
return drop_type{regression.get_program().get_random().get_float(-1.0f, 1.0f), true};
}, "lit").set_ephemeral();
auto litf = operation_t([]()
{
return regression.get_program().get_random().get_float(-1.0f, 1.0f);
}, "litf").set_ephemeral();
operation_t op_x([](const context& context)
{
return drop_type{context.x};
}, "x");
operation_t op_xf([](const context& context)
{
return context.x;
}, "xf");
bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t)
{
if (normal_construct - normal_drop > max_allocated)
max_allocated = normal_construct - normal_drop;
constexpr static double value_cutoff = 1.e15;
for (auto& fitness_case : regression.get_training_cases())
{
BLT_GP_UPDATE_CONTEXT(fitness_case);
auto val = current_tree.get_evaluation_ref<drop_type>(fitness_case);
const auto diff = std::abs(fitness_case.y - val.get().value());
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) == regression.get_training_cases().size();
}
int main()
{
operator_builder<context> builder{};
builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, op_mixed_input, lit, op_x, addf, subf, mulf, pro_divf, op_sinf, op_cosf,
op_expf, op_logf,
litf, op_xf, op_tof, op_todrop);
regression.get_program().set_operations(builder.grab());
auto& program = regression.get_program();
static auto sel = select_tournament_t{};
program.generate_initial_population(program.get_typesystem().get_type<drop_type>().id());
program.setup_generational_evaluation(fitness_function, sel, sel, sel);
while (!program.should_terminate())
{
BLT_TRACE("---------------\\{Begin Generation {}}---------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
}
// program.get_best_individuals<1>()[0].get().tree.print(program, std::cout, true, true);
regression.get_program().get_current_pop().clear();
regression.get_program().next_generation();
regression.get_program().get_current_pop().clear();
BLT_TRACE("Created {} times", normal_construct.load());
BLT_TRACE("Dropped {} times", normal_drop.load());
BLT_TRACE("Ephemeral created {} times", ephemeral_construct.load());
BLT_TRACE("Ephemeral dropped {} times", ephemeral_drop.load());
BLT_TRACE("Max allocated {} times", max_allocated.load());
}

168
tests/drop_test.cpp Normal file
View File

@ -0,0 +1,168 @@
/*
* <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 <blt/gp/program.h>
#include <blt/logging/logging.h>
using namespace blt::gp;
std::atomic_uint64_t normal_construct = 0;
std::atomic_uint64_t ephemeral_construct = 0;
std::atomic_uint64_t normal_drop = 0;
std::atomic_uint64_t ephemeral_drop = 0;
std::atomic_uint64_t max_allocated = 0;
struct drop_type
{
float* m_value;
bool ephemeral = false;
drop_type() : m_value(new float(0))
{
++normal_construct;
}
explicit drop_type(const float silly) : m_value(new float(silly))
{
++normal_construct;
}
explicit drop_type(const float silly, bool) : m_value(new float(silly)), ephemeral(true)
{
// BLT_TRACE("Constructor with value %f", silly);
++ephemeral_construct;
}
[[nodiscard]] float value() const
{
return *m_value;
}
void drop() const
{
if (ephemeral)
{
std::cout << ("Ephemeral drop") << std::endl;
++ephemeral_drop;
}else
++normal_drop;
delete m_value;
}
friend std::ostream& operator<<(std::ostream& os, const drop_type& dt)
{
os << dt.m_value;
return os;
}
};
struct context
{
float x, y;
};
prog_config_t config = prog_config_t()
.set_initial_min_tree_size(2)
.set_initial_max_tree_size(6)
.set_elite_count(2)
.set_crossover_chance(0.8)
.set_mutation_chance(0.0)
.set_reproduction_chance(0.1)
.set_max_generations(50)
.set_pop_size(500)
.set_thread_count(0);
example::symbolic_regression_t regression{691ul, config};
operation_t add{[](const drop_type a, const drop_type b) { return drop_type{a.value() + b.value()}; }, "add"};
operation_t sub([](const drop_type a, const drop_type b) { return drop_type{a.value() - b.value()}; }, "sub");
operation_t mul([](const drop_type a, const drop_type b) { return drop_type{a.value() * b.value()}; }, "mul");
operation_t pro_div([](const drop_type a, const drop_type b) { return drop_type{b.value() == 0.0f ? 0.0f : a.value() / b.value()}; }, "div");
operation_t op_sin([](const drop_type a) { return drop_type{std::sin(a.value())}; }, "sin");
operation_t op_cos([](const drop_type a) { return drop_type{std::cos(a.value())}; }, "cos");
operation_t op_exp([](const drop_type a) { return drop_type{std::exp(a.value())}; }, "exp");
operation_t op_log([](const drop_type a) { return drop_type{a.value() <= 0.0f ? 0.0f : std::log(a.value())}; }, "log");
auto lit = operation_t([]()
{
return drop_type{regression.get_program().get_random().get_float(-1.0f, 1.0f), true};
}, "lit").set_ephemeral();
operation_t op_x([](const context& context)
{
return drop_type{context.x};
}, "x");
bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t)
{
if (normal_construct - normal_drop > max_allocated)
max_allocated = normal_construct - normal_drop;
constexpr static double value_cutoff = 1.e15;
for (auto& fitness_case : regression.get_training_cases())
{
BLT_GP_UPDATE_CONTEXT(fitness_case);
auto val = current_tree.get_evaluation_ref<drop_type>(fitness_case);
const auto diff = std::abs(fitness_case.y - val.get().value());
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) == regression.get_training_cases().size();
}
int main()
{
operator_builder<context> builder{};
builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x);
regression.get_program().set_operations(builder.grab());
auto& program = regression.get_program();
static auto sel = select_tournament_t{};
program.generate_initial_population(program.get_typesystem().get_type<drop_type>().id());
program.setup_generational_evaluation(fitness_function, sel, sel, sel);
while (!program.should_terminate())
{
BLT_TRACE("---------------\\{Begin Generation {}}---------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
}
// program.get_best_individuals<1>()[0].get().tree.print(program, std::cout, true, true);
regression.get_program().get_current_pop().clear();
regression.get_program().next_generation();
regression.get_program().get_current_pop().clear();
BLT_TRACE("Created {} times", normal_construct.load());
BLT_TRACE("Dropped {} times", normal_drop.load());
BLT_TRACE("Ephemeral created {} times", ephemeral_construct.load());
BLT_TRACE("Ephemeral dropped {} times", ephemeral_drop.load());
BLT_TRACE("Max allocated {} times", max_allocated.load());
}

View File

@ -22,7 +22,7 @@
#include <blt/gp/program.h>
#include <random>
const blt::u64 SEED = std::random_device()();
static const auto SEED_FUNC = [] { return std::random_device()(); };
struct large_256
{
@ -52,13 +52,12 @@ struct large_18290
large_18290 base{};
blt::gp::type_provider type_system;
blt::gp::gp_program program{type_system, SEED};
blt::gp::gp_program program{SEED_FUNC};
blt::gp::op_container_t make_container(blt::gp::operator_id id)
{
auto& info = program.get_operator_info(id);
return {type_system.get_type(info.return_type).size(), id, false};
return {program.get_typesystem().get_type(info.return_type).size(), id, false};
}
blt::gp::op_container_t make_value(const blt::gp::type& id)
@ -107,12 +106,12 @@ void basic_tree()
blt::gp::tree_t tree{program};
tree.get_operations().push_back(make_container(sub.id));
tree.get_operations().push_back(make_value(type_system.get_type<float>()));
tree.get_operations().push_back(make_value(type_system.get_type<float>()));
tree.get_operations().push_back(make_value(program.get_typesystem().get_type<float>()));
tree.get_operations().push_back(make_value(program.get_typesystem().get_type<float>()));
tree.get_values().push(50.0f);
tree.get_values().push(120.0f);
auto val = tree.get_evaluation_value<float>(nullptr);
auto val = tree.get_evaluation_value<float>();
BLT_TRACE(val);
BLT_ASSERT(val == (120 - 50));
}
@ -125,16 +124,16 @@ void large_cross_type_tree()
ops.push_back(make_container(cross_large_type.id));
ops.push_back(make_container(sub.id));
ops.push_back(make_value(type_system.get_type<float>()));
ops.push_back(make_value(type_system.get_type<float>()));
ops.push_back(make_value(type_system.get_type<float>()));
ops.push_back(make_value(program.get_typesystem().get_type<float>()));
ops.push_back(make_value(program.get_typesystem().get_type<float>()));
ops.push_back(make_value(program.get_typesystem().get_type<float>()));
ops.push_back(make_container(large_literal.id));
vals.push(50.0f);
vals.push(120.0f);
vals.push(5555.0f);
auto val = tree.get_evaluation_value<large_18290>(nullptr);
auto val = tree.get_evaluation_value<large_18290>();
blt::black_box(val);
}
@ -143,11 +142,7 @@ int main()
for (auto& v : base.data)
v = static_cast<blt::u8>(blt::random::murmur_random64c(691, std::numeric_limits<blt::u8>::min(), std::numeric_limits<blt::u8>::max()));
type_system.register_type<float>();
type_system.register_type<bool>();
type_system.register_type<large_18290>();
blt::gp::operator_builder builder{type_system};
blt::gp::operator_builder builder{};
program.set_operations(builder.build(f_literal, b_literal, add, basic_2t, sub, large_literal, cross_large_type));
basic_tree();

View File

@ -438,9 +438,7 @@ int main()
return ctx.x;
});
blt::gp::type_provider system;
system.register_type<float>();
blt::gp::operator_builder<context> ops{system};
blt::gp::operator_builder<context> ops{};
//BLT_TRACE(blt::type_string<decltype(silly_op_3)::first::type>());
//BLT_TRACE(typeid(decltype(silly_op_3)::first::type).name());

View File

@ -21,10 +21,9 @@
#include <blt/std/logging.h>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED); // NOLINT
blt::gp::gp_program program(SEED_FUNC); // NOLINT
blt::gp::operation_t add([](float a, float b) {
BLT_TRACE("a: %f + b: %f = %f", a, b, a + b);
@ -53,16 +52,15 @@ auto lit = blt::gp::operation_t([]() {
*/
int main()
{
type_system.register_type<float>();
blt::gp::operator_builder silly{type_system};
blt::gp::operator_builder silly{};
program.set_operations(silly.build(add, sub, mul, pro_div, lit));
blt::gp::grow_generator_t grow;
auto tree = grow.generate(blt::gp::generator_arguments{program, type_system.get_type<float>().id(), 3, 7});
blt::gp::tree_t tree{program};
grow.generate(tree, blt::gp::generator_arguments{program, program.get_typesystem().get_type<float>().id(), 3, 7});
auto value = tree.get_evaluation_value<float>(nullptr);
auto value = tree.get_evaluation_value<float>();
BLT_TRACE(value);

View File

@ -20,10 +20,9 @@
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED); // NOLINT
blt::gp::gp_program program(SEED_FUNC); // NOLINT
blt::gp::operation_t add([](float a, float b) { return a + b; });
blt::gp::operation_t sub([](float a, float b) { return a - b; });
@ -51,16 +50,14 @@ auto lit = blt::gp::operation_t([]() {
*/
int main()
{
type_system.register_type<float>();
type_system.register_type<bool>();
blt::gp::operator_builder silly{type_system};
blt::gp::operator_builder silly{};
program.set_operations(silly.build(add, sub, mul, pro_div, op_if, eq_f, eq_b, lt, gt, op_and, op_or, op_xor, op_not, lit));
blt::gp::grow_generator_t grow;
auto tree = grow.generate(blt::gp::generator_arguments{program, type_system.get_type<float>().id(), 3, 7});
blt::gp::tree_t tree{program};
grow.generate(tree, blt::gp::generator_arguments{program, program.get_typesystem().get_type<float>().id(), 3, 7});
auto value = tree.get_evaluation_value<float>(nullptr);
auto value = tree.get_evaluation_value<float>();
BLT_TRACE(value);

View File

@ -20,10 +20,9 @@
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED); // NOLINT
blt::gp::gp_program program(SEED_FUNC); // NOLINT
blt::gp::operation_t add([](float a, float b) { return a + b; });
blt::gp::operation_t sub([](float a, float b) { return a - b; });
@ -51,19 +50,16 @@ auto lit = blt::gp::operation_t([]() {
*/
int main()
{
type_system.register_type<float>();
type_system.register_type<bool>();
blt::gp::operator_builder builder{type_system};
blt::gp::operator_builder builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_if, eq_f, eq_b, lt, gt, op_and, op_or, op_xor, op_not, lit));
blt::gp::ramped_half_initializer_t pop_init;
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type<float>().id(), 500, 3, 10});
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, program.get_typesystem().get_type<float>().id(), 500, 3, 10});
for (auto& tree : pop.for_each_tree())
{
auto value = tree.get_evaluation_value<float>(nullptr);
auto value = tree.get_evaluation_value<float>();
BLT_TRACE(value);
}

View File

@ -40,11 +40,9 @@
#include <string_view>
#include <iostream>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED); // NOLINT
blt::gp::gp_program program(SEED_FUNC); // NOLINT
blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); // 0
blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); // 1
@ -72,15 +70,12 @@ auto lit = blt::gp::operation_t([]() {
*/
int main()
{
type_system.register_type<float>();
type_system.register_type<bool>();
blt::gp::operator_builder builder{type_system};
blt::gp::operator_builder builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_if, eq_f, eq_b, lt, gt, op_and, op_or, op_xor, op_not, lit));
blt::gp::ramped_half_initializer_t pop_init;
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type<float>().id(), 500, 3, 10});
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, program.get_typesystem().get_type<float>().id(), 500, 3, 10});
// for (auto& tree : pop.getIndividuals())
// {
@ -99,7 +94,7 @@ int main()
BLT_INFO("Pre-Crossover:");
for (auto& tree : pop.get_individuals())
{
auto f = tree.tree.get_evaluation_value<float>(nullptr);
auto f = tree.tree.get_evaluation_value<float>();
pre.push_back(f);
BLT_TRACE(f);
}
@ -116,34 +111,31 @@ int main()
second = random.get_size_t(0ul, pop.get_individuals().size());
} while (second == first);
auto results = crossover.apply(program, ind[first].tree, ind[second].tree);
if (results.has_value())
blt::gp::tree_t child1{program};
blt::gp::tree_t child2{program};
// crossover function assumes that children have been copied from parents
child1.copy_fast(ind[first].tree);
child2.copy_fast(ind[second].tree);
auto results = crossover.apply(program, ind[first].tree, ind[second].tree, child1, child2);
if (results)
{
// bool print_literals = true;
// bool pretty_print = false;
// bool print_returns = false;
// BLT_TRACE("Parent 1: %f", ind[0].get_evaluation_value<float>(nullptr));
// BLT_TRACE("Parent 1: %f", ind[0].get_evaluation_value<float>());
// ind[0].print(program, std::cout, print_literals, pretty_print, print_returns);
// BLT_TRACE("Parent 2: %f", ind[1].get_evaluation_value<float>(nullptr));
// BLT_TRACE("Parent 2: %f", ind[1].get_evaluation_value<float>());
// ind[1].print(program, std::cout, print_literals, pretty_print, print_returns);
// BLT_TRACE("------------");
// BLT_TRACE("Child 1: %f", results->child1.get_evaluation_value<float>(nullptr));
// BLT_TRACE("Child 1: %f", results->child1.get_evaluation_value<float>());
// results->child1.print(program, std::cout, print_literals, pretty_print, print_returns);
// BLT_TRACE("Child 2: %f", results->child2.get_evaluation_value<float>(nullptr));
// BLT_TRACE("Child 2: %f", results->child2.get_evaluation_value<float>());
// results->child2.print(program, std::cout, print_literals, pretty_print, print_returns);
new_pop.get_individuals().emplace_back(std::move(results->child1));
new_pop.get_individuals().emplace_back(std::move(results->child2));
new_pop.get_individuals().emplace_back(std::move(child1));
new_pop.get_individuals().emplace_back(std::move(child2));
} else
{
switch (results.error())
{
case blt::gp::crossover_t::error_t::NO_VALID_TYPE:
BLT_DEBUG("No valid type!");
break;
case blt::gp::crossover_t::error_t::TREE_TOO_SMALL:
BLT_DEBUG("Tree is too small!");
break;
}
BLT_DEBUG("Crossover Failed.");
errors++;
new_pop.get_individuals().push_back(ind[first]);
new_pop.get_individuals().push_back(ind[second]);
@ -153,7 +145,7 @@ int main()
BLT_INFO("Post-Crossover:");
for (auto& tree : new_pop.for_each_tree())
{
auto f = tree.get_evaluation_value<float>(nullptr);
auto f = tree.get_evaluation_value<float>();
pos.push_back(f);
BLT_TRACE(f);
}

View File

@ -38,11 +38,9 @@
#include <blt/std/logging.h>
#include <blt/gp/transformers.h>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED); // NOLINT
blt::gp::gp_program program(SEED_FUNC); // NOLINT
blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); // 0
blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); // 1
@ -70,15 +68,12 @@ auto lit = blt::gp::operation_t([]() {
*/
int main()
{
type_system.register_type<float>();
type_system.register_type<bool>();
blt::gp::operator_builder builder{type_system};
blt::gp::operator_builder builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_if, eq_f, eq_b, lt, gt, op_and, op_or, op_xor, op_not, lit));
blt::gp::ramped_half_initializer_t pop_init;
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, type_system.get_type<float>().id(), 500, 3, 10});
auto pop = pop_init.generate(blt::gp::initializer_arguments{program, program.get_typesystem().get_type<float>().id(), 500, 3, 10});
blt::gp::population_t new_pop;
blt::gp::mutation_t mutator;
@ -90,19 +85,22 @@ int main()
BLT_INFO("Pre-Mutation:");
for (auto& tree : pop.for_each_tree())
{
auto f = tree.get_evaluation_value<float>(nullptr);
auto f = tree.get_evaluation_value<float>();
pre.push_back(f);
BLT_TRACE(f);
}
BLT_INFO("Mutation:");
for (auto& tree : pop.for_each_tree())
{
new_pop.get_individuals().emplace_back(mutator.apply(program, tree));
blt::gp::tree_t tree_out{program};
tree_out.copy_fast(tree);
mutator.apply(program, tree, tree_out);
new_pop.get_individuals().emplace_back(std::move(tree_out));
}
BLT_INFO("Post-Mutation");
for (auto& tree : new_pop.for_each_tree())
{
auto f = tree.get_evaluation_value<float>(nullptr);
auto f = tree.get_evaluation_value<float>();
pos.push_back(f);
BLT_TRACE(f);
}

View File

@ -19,12 +19,11 @@
#include <blt/gp/tree.h>
#include <blt/std/logging.h>
static constexpr long SEED = 41912;
static const auto SEED_FUNC = [] { return std::random_device()(); };
blt::gp::prog_config_t config = blt::gp::prog_config_t().set_elite_count(2);
blt::gp::type_provider type_system;
blt::gp::gp_program program(type_system, SEED, config); // NOLINT
blt::gp::gp_program program(SEED_FUNC, config); // NOLINT
std::array<float, 500> result_container;
blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); // 0
@ -59,7 +58,7 @@ void print_best()
auto& tree = v.tree;
auto size = tree.get_values().size();
BLT_TRACE("%lf [index %ld] (fitness: %lf, raw: %lf) (depth: %ld) (size: t: %ld u: %ld r: %ld) filled: %f%%",
tree.get_evaluation_value<float>(nullptr), i, v.fitness.adjusted_fitness, v.fitness.raw_fitness,
tree.get_evaluation_value<float>(), i, v.fitness.adjusted_fitness, v.fitness.raw_fitness,
tree.get_depth(program), size.total_size_bytes, size.total_used_bytes,
size.total_remaining_bytes,
static_cast<double>(size.total_used_bytes) / (size.total_size_bytes == 0 ? 1 : static_cast<double>(size.total_size_bytes)));
@ -75,7 +74,7 @@ constexpr auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fit
BLT_DEBUG("(depth: %ld) (blocks: %ld) (size: t: %ld m: %ld u: %ld r: %ld) filled: %f%%",
current_tree.get_depth(program), size.blocks, size.total_size_bytes, size.total_no_meta_bytes, size.total_used_bytes,
size.total_remaining_bytes, static_cast<double>(size.total_used_bytes) / static_cast<double>(size.total_no_meta_bytes));*/
result_container[index] = current_tree.get_evaluation_value<float>(nullptr);
result_container[index] = current_tree.get_evaluation_value<float>();
fitness.raw_fitness = result_container[index] / 1000000000.0;
fitness.standardized_fitness = fitness.raw_fitness;
fitness.adjusted_fitness = 1.0 - (1.0 / (1.0 + fitness.raw_fitness));
@ -86,14 +85,11 @@ constexpr auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fit
*/
int main()
{
type_system.register_type<float>();
type_system.register_type<bool>();
blt::gp::operator_builder builder{type_system};
blt::gp::operator_builder builder{};
program.set_operations(builder.build(add, sub, mul, pro_div, op_if, eq_f, eq_b, lt, gt, op_and, op_or, op_xor, op_not, lit));
auto sel = blt::gp::select_tournament_t{};
program.generate_population(type_system.get_type<float>().id(), fitness_function, sel, sel, sel);
program.generate_population(program.get_typesystem().get_type<float>().id(), fitness_function, sel, sel, sel);
while (!program.should_terminate())
{

View File

@ -27,7 +27,6 @@
const blt::u64 SEED = std::random_device()();
//const blt::u64 SEED = 3495535167;
blt::gp::random_t b_rand {SEED};
blt::gp::type_provider type_system;
struct context
{
@ -82,18 +81,19 @@ auto basic_lit_b = blt::gp::operation_t([]() {
void basic_test()
{
blt::gp::gp_program program{type_system, SEED};
blt::gp::gp_program program{SEED};
blt::gp::operator_builder<context> builder{type_system};
blt::gp::operator_builder<context> builder{};
program.set_operations(builder.build(basic_sub, basic_lit_f, basic_lit_b));
blt::gp::grow_generator_t gen;
blt::gp::generator_arguments args{program, type_system.get_type<float>().id(), 1, 1};
auto tree = gen.generate(args);
blt::gp::generator_arguments args{program, program.get_typesystem().get_type<float>().id(), 1, 1};
blt::gp::tree_t tree{program};
gen.generate(tree, args);
context ctx{&program};
auto result = tree.get_evaluation_value<float>(&ctx);
auto result = tree.get_evaluation_value<float>(ctx);
BLT_TRACE(result);
BLT_ASSERT(result == -5.0f || result == 5.0f || result == 0.0f);
tree.print(program, std::cout, true, true);
@ -103,9 +103,5 @@ int main()
{
BLT_INFO("Starting with seed %ld", SEED);
type_system.register_type<float>();
type_system.register_type<bool>();
type_system.register_type<large_18290>();
basic_test();
}

View File

@ -0,0 +1,171 @@
/*
* <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 <filesystem>
#include "../examples/symbolic_regression.h"
#include <blt/gp/program.h>
#include <blt/logging/logging.h>
#include <ostream>
#include <istream>
#include <fstream>
#include <blt/fs/stream_wrappers.h>
using namespace blt::gp;
struct context
{
float x, y;
};
prog_config_t config = prog_config_t()
.set_initial_min_tree_size(2)
.set_initial_max_tree_size(6)
.set_elite_count(2)
.set_crossover_chance(0.8)
.set_mutation_chance(0.1)
.set_reproduction_chance(0.1)
.set_max_generations(50)
.set_pop_size(500)
.set_thread_count(1);
example::symbolic_regression_t regression{691ul, config};
operation_t addf{[](const float a, const float b) { return a + b; }, "addf"};
operation_t subf([](const float a, const float b) { return a - b; }, "subf");
operation_t mulf([](const float a, const float b) { return a * b; }, "mulf");
operation_t pro_divf([](const float a, const float b) { return b == 0.0f ? 0.0f : a / b; }, "divf");
operation_t op_sinf([](const float a) { return std::sin(a); }, "sinf");
operation_t op_cosf([](const float a) { return std::cos(a); }, "cosf");
operation_t op_expf([](const float a) { return std::exp(a); }, "expf");
operation_t op_logf([](const float a) { return a <= 0.0f ? 0.0f : std::log(a); }, "logf");
auto litf = operation_t([]()
{
return regression.get_program().get_random().get_float(-1.0f, 1.0f);
}, "litf").set_ephemeral();
operation_t op_xf([](const context& context)
{
return context.x;
}, "xf");
bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t)
{
constexpr static double value_cutoff = 1.e15;
for (auto& fitness_case : regression.get_training_cases())
{
BLT_GP_UPDATE_CONTEXT(fitness_case);
auto val = current_tree.get_evaluation_ref<float>(fitness_case);
const auto diff = std::abs(fitness_case.y - val.get());
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) == regression.get_training_cases().size();
}
int main()
{
operator_builder<context> builder{};
const auto& operators = builder.build(addf, subf, mulf, pro_divf, op_sinf, op_cosf, op_expf, op_logf, litf, op_xf);
regression.get_program().set_operations(operators);
auto& program = regression.get_program();
static auto sel = select_tournament_t{};
gp_program test_program{691};
test_program.set_operations(operators);
test_program.setup_generational_evaluation(fitness_function, sel, sel, sel, false);
// simulate a program which is similar but incompatible with the other programs.
operator_builder<context> builder2{};
gp_program bad_program{691};
bad_program.set_operations(builder2.build(addf, subf, mulf, op_sinf, op_cosf, litf, op_xf));
bad_program.setup_generational_evaluation(fitness_function, sel, sel, sel, false);
program.generate_initial_population(program.get_typesystem().get_type<float>().id());
program.setup_generational_evaluation(fitness_function, sel, sel, sel);
while (!program.should_terminate())
{
BLT_TRACE("---------------\\{Begin Generation {}}---------------", program.get_current_generation());
BLT_TRACE("Creating next generation");
program.create_next_generation();
BLT_TRACE("Move to next generation");
program.next_generation();
BLT_TRACE("Evaluate Fitness");
program.evaluate_fitness();
{
std::ofstream stream{"serialization_test.data", std::ios::binary | std::ios::trunc};
blt::fs::fstream_writer_t writer{stream};
program.save_generation(writer);
}
{
std::ifstream stream{"serialization_test.data", std::ios::binary};
blt::fs::fstream_reader_t reader{stream};
test_program.load_generation(reader);
}
// do a quick validation check
for (const auto& [saved, loaded] : blt::zip(program.get_current_pop(), test_program.get_current_pop()))
{
if (saved.tree != loaded.tree)
{
BLT_ERROR("Serializer Failed to correctly serialize tree to disk, trees are not equal!");
std::exit(1);
}
}
}
{
std::ofstream stream{"serialization_test2.data", std::ios::binary | std::ios::trunc};
blt::fs::fstream_writer_t writer{stream};
program.save_state(writer);
}
{
std::ifstream stream{"serialization_test2.data", std::ios::binary};
blt::fs::fstream_reader_t reader{stream};
if (auto error = test_program.load_state(reader))
{
BLT_ERROR("Error: {}", error->call_member(&errors::serialization::error_to_string_t::to_string));
BLT_ABORT("Expected program to succeeded without returning an error state!");
}
for (const auto [saved, loaded] : blt::zip(program.get_stats_histories(), test_program.get_stats_histories()))
{
if (saved != loaded)
{
BLT_ERROR("Serializer Failed to correctly serialize histories to disk, histories are not equal!");
std::exit(1);
}
}
}
{
std::ifstream stream{"serialization_test2.data", std::ios::binary};
blt::fs::fstream_reader_t reader{stream};
if (!bad_program.load_state(reader))
{
BLT_ABORT("Expected program to throw an exception when parsing state data into an incompatible program!");
}
}
}

View File

@ -0,0 +1,203 @@
/*
* <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 <sstream>
#include <blt/profiling/profiler_v2.h>
static const auto SEED_FUNC = [] { return std::random_device()(); };
std::array crossover_chances = {0.8, 0.9, 1.0};
std::array mutation_chances = {0.0, 0.1, 0.2, 0.9, 1.0};
std::array reproduction_chances = {0.0, 0.1, 0.9, 1.0};
std::array elite_amounts = {0, 2, 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 {}}------------", 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: {}, Best Fit: {}, Worst Fit: {}, Overall Fit: {}",
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;
}
}
void do_run()
{
std::stringstream results;
for (const auto crossover_chance : crossover_chances)
{
for (const auto mutation_chance : mutation_chances)
{
for (const auto reproduction_chance : reproduction_chances)
{
if (crossover_chance == 0 && mutation_chance == 0 && reproduction_chance == 0)
continue;
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("Run: Crossover ({}) Mutation ({}) Reproduction ({}) Elite ({}) Population Size ({})", crossover_chance,
mutation_chance, reproduction_chance, elite_amount, population_sizes);
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;
}
template <typename What, typename What2>
auto what(What addr, What2 addr2) -> decltype(addr + addr2)
{
return addr + addr2;
}
enum class test
{
hello,
there
};
inline void hello(blt::size_t)
{
BLT_TRACE("I did some parallel work!");
}
inline void there(blt::size_t)
{
BLT_TRACE("Wow there");
}
int main()
{
// blt::gp::thread_manager_t threads{
// std::thread::hardware_concurrency(), blt::gp::task_builder_t<test>::make_callable(
// blt::gp::task_t{test::hello, hello},
// blt::gp::task_t{test::there, there}
// )
// };
// threads.add_task(test::hello);
// threads.add_task(test::hello);
// threads.add_task(test::hello);
// threads.add_task(test::there);
// while (threads.has_tasks_left())
// threads.execute();
for (int i = 0; i < 1; i++)
do_run();
BLT_PRINT_PROFILE("Symbolic Regress");
return 0;
}

View File

@ -1,41 +0,0 @@
Performance counter stats for './cmake-build-release/blt-symbolic-regression-example' (30 runs):
35,671,860,546 branches ( +- 5.05% ) (20.11%)
130,603,525 branch-misses # 0.37% of all branches ( +- 4.61% ) (20.67%)
43,684,408 cache-misses # 9.61% of all cache refs ( +- 3.08% ) (20.97%)
454,604,804 cache-references ( +- 4.53% ) (21.30%)
72,861,649,501 cycles ( +- 5.33% ) (22.00%)
170,811,735,018 instructions # 2.34 insn per cycle ( +- 5.59% ) (22.84%)
0 alignment-faults
33,002 cgroup-switches ( +- 1.71% )
293,932 faults ( +- 4.09% )
1,130,322,318 ns duration_time ( +- 3.73% )
16,750,942,537 ns user_time ( +- 1.71% )
1,165,192,903 ns system_time ( +- 0.87% )
57,551,179,178 L1-dcache-loads ( +- 5.63% ) (22.36%)
214,283,064 L1-dcache-load-misses # 0.37% of all L1-dcache accesses ( +- 5.58% ) (22.13%)
75,685,527 L1-dcache-prefetches ( +- 7.55% ) (22.07%)
1,115,360,458 L1-icache-loads ( +- 3.91% ) (21.67%)
2,868,754 L1-icache-load-misses # 0.26% of all L1-icache accesses ( +- 3.34% ) (21.34%)
65,107,178 dTLB-loads ( +- 8.94% ) (21.00%)
4,971,480 dTLB-load-misses # 7.64% of all dTLB cache accesses ( +- 3.70% ) (20.90%)
452,351 iTLB-loads ( +- 4.80% ) (20.62%)
1,600,933 iTLB-load-misses # 353.91% of all iTLB cache accesses ( +- 3.68% ) (20.62%)
332,075,460 l2_request_g1.all_no_prefetch ( +- 4.59% ) (20.73%)
293,932 page-faults ( +- 4.09% )
293,928 page-faults:u ( +- 4.09% )
3 page-faults:k ( +- 4.92% )
58,806,652,381 L1-dcache-loads ( +- 5.44% ) (20.61%)
216,591,223 L1-dcache-load-misses # 0.38% of all L1-dcache accesses ( +- 5.39% ) (21.02%)
<not supported> LLC-loads
<not supported> LLC-load-misses
1,059,748,012 L1-icache-loads ( +- 4.29% ) (21.55%)
2,615,017 L1-icache-load-misses # 0.23% of all L1-icache accesses ( +- 3.34% ) (21.85%)
65,917,126 dTLB-loads ( +- 8.89% ) (21.78%)
4,717,351 dTLB-load-misses # 7.25% of all dTLB cache accesses ( +- 3.52% ) (22.05%)
459,796 iTLB-loads ( +- 5.92% ) (21.77%)
1,512,986 iTLB-load-misses # 334.47% of all iTLB cache accesses ( +- 3.64% ) (21.26%)
74,656,433 L1-dcache-prefetches ( +- 7.94% ) (20.50%)
<not supported> L1-dcache-prefetch-misses
1.1303 +- 0.0422 seconds time elapsed ( +- 3.73% )