Compare commits

..

4 Commits

Author SHA1 Message Date
Brett 1789a9e474 i think it's shared now? 2024-08-31 22:03:22 -04:00
Brett 3572539b74 silly2 2024-08-31 00:10:25 -04:00
Brett 2f3ea1b3bf silly 2024-08-31 00:05:04 -04:00
Brett 91bda10b0a meow 2024-08-30 23:33:52 -04:00
8 changed files with 150 additions and 111 deletions

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.25)
project(blt-gp VERSION 0.1.35)
project(blt-gp VERSION 0.1.36)
include(CTest)
@ -16,7 +16,7 @@ set(CMAKE_CXX_STANDARD 17)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -g")
#SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -g")
if (NOT TARGET BLT)
add_subdirectory(lib/blt)

View File

@ -36,18 +36,18 @@ 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(2)
.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(500)
.set_pop_size(20000)
.set_thread_count(0);
blt::gp::gp_program program{SEED, config};
auto lit = blt::gp::operation_t([]() {
return program.get_random().get_float(-320.0f, 320.0f);
return program.get_random().get_float(-1.0f, 1.0f);
}, "lit").set_ephemeral();
blt::gp::operation_t op_x([](const context& context) {

View File

@ -362,8 +362,11 @@ namespace blt::gp
#ifdef BLT_TRACK_ALLOCATIONS
auto gen_alloc = blt::gp::tracker.start_measurement();
#endif
BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
// should already be empty
next_pop.clear();
thread_helper.next_gen_left.store(config.population_size, std::memory_order_release);
(*thread_execution_service)(0);
#ifdef BLT_TRACK_ALLOCATIONS
@ -375,8 +378,10 @@ namespace blt::gp
void next_generation()
{
BLT_ASSERT_MSG(current_pop.get_individuals().size() == config.population_size,
("cur pop size: " + std::to_string(current_pop.get_individuals().size())).c_str());
BLT_ASSERT_MSG(next_pop.get_individuals().size() == config.population_size,
("pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
("next pop size: " + std::to_string(next_pop.get_individuals().size())).c_str());
std::swap(current_pop, next_pop);
current_generation++;
}
@ -400,6 +405,7 @@ namespace blt::gp
current_generation = 0;
current_pop = config.pop_initializer.get().generate(
{*this, root_type, config.population_size, config.initial_min_tree_size, config.initial_max_tree_size});
next_pop = population_t(current_pop);
if (eval_fitness_now)
evaluate_fitness_internal();
}
@ -465,21 +471,24 @@ namespace blt::gp
}
if (thread_helper.next_gen_left > 0)
{
static thread_local tracked_vector<tree_t> new_children;
new_children.clear();
auto args = get_selector_args(new_children);
auto args = get_selector_args();
crossover_selection.pre_process(*this, current_pop);
mutation_selection.pre_process(*this, current_pop);
reproduction_selection.pre_process(*this, current_pop);
perform_elitism(args);
perform_elitism(args, next_pop);
while (new_children.size() < config.population_size)
func(args, crossover_selection, mutation_selection, reproduction_selection);
blt::size_t start = config.elites;
for (auto& i : new_children)
next_pop.get_individuals().emplace_back(std::move(i));
while (start < config.population_size)
{
tree_t& c1 = next_pop.get_individuals()[start].tree;
tree_t* c2 = nullptr;
if (start + 1 < config.population_size)
c2 = &next_pop.get_individuals()[start + 1].tree;
start += func(args, crossover_selection, mutation_selection, reproduction_selection, c1, c2);
}
thread_helper.next_gen_left = 0;
}
@ -542,9 +551,7 @@ namespace blt::gp
}
if (thread_helper.next_gen_left > 0)
{
static thread_local tracked_vector<tree_t> new_children;
new_children.clear();
auto args = get_selector_args(new_children);
auto args = get_selector_args();
if (id == 0)
{
current_stats.normalized_fitness.clear();
@ -562,37 +569,35 @@ namespace blt::gp
if (&crossover_selection != &reproduction_selection)
reproduction_selection.pre_process(*this, current_pop);
perform_elitism(args);
perform_elitism(args, next_pop);
for (auto& i : new_children)
next_pop.get_individuals().emplace_back(std::move(i));
thread_helper.next_gen_left -= new_children.size();
new_children.clear();
thread_helper.next_gen_left -= config.elites;
}
thread_helper.barrier.wait();
while (thread_helper.next_gen_left > 0)
{
blt::size_t size = 0;
blt::size_t begin = 0;
blt::size_t end = thread_helper.next_gen_left.load(std::memory_order_relaxed);
do
{
size = std::min(end, config.evaluation_size);
begin = end - size;
} while (!thread_helper.next_gen_left.compare_exchange_weak(end, end - size,
std::memory_order::memory_order_relaxed,
std::memory_order::memory_order_relaxed));
while (new_children.size() < size)
func(args, crossover_selection, mutation_selection, reproduction_selection);
while (begin != end)
{
std::scoped_lock lock(thread_helper.thread_generation_lock);
for (auto& i : new_children)
{
if (next_pop.get_individuals().size() < config.population_size)
next_pop.get_individuals().emplace_back(i);
}
auto index = config.elites + begin;
tree_t& c1 = next_pop.get_individuals()[index].tree;
tree_t* c2 = nullptr;
if (index + 1 < end)
c2 = &next_pop.get_individuals()[index + 1].tree;
begin += func(args, crossover_selection, mutation_selection, reproduction_selection, c1, c2);
}
}
}
thread_helper.barrier.wait();
@ -747,9 +752,9 @@ namespace blt::gp
}
private:
inline selector_args get_selector_args(tracked_vector<tree_t>& next_pop_trees)
inline selector_args get_selector_args()
{
return {*this, next_pop_trees, current_pop, current_stats, config, get_random()};
return {*this, current_pop, current_stats, config, get_random()};
}
template<typename Return, blt::size_t size, typename Accessor, blt::size_t... indexes>
@ -765,7 +770,7 @@ namespace blt::gp
{
statistic_history.push_back(current_stats);
current_stats.clear();
thread_helper.evaluation_left.store(current_pop.get_individuals().size(), std::memory_order_release);
thread_helper.evaluation_left.store(config.population_size, std::memory_order_release);
(*thread_execution_service)(0);
current_stats.average_fitness = current_stats.overall_fitness / static_cast<double>(config.population_size);
@ -791,7 +796,6 @@ namespace blt::gp
std::vector<std::unique_ptr<std::thread>> threads;
std::mutex thread_function_control{};
std::mutex thread_generation_lock{};
std::condition_variable thread_function_condition{};
std::atomic_uint64_t evaluation_left = 0;

View File

@ -32,15 +32,14 @@ namespace blt::gp
struct selector_args
{
gp_program& program;
tracked_vector<tree_t>& next_pop;
population_t& current_pop;
const population_t& current_pop;
population_stats& current_stats;
prog_config_t& config;
random_t& random;
};
constexpr inline auto perform_elitism = [](const selector_args& args) {
auto& [program, next_pop, current_pop, current_stats, config, random] = args;
constexpr inline auto perform_elitism = [](const selector_args& args, population_t& next_pop) {
auto& [program, current_pop, current_stats, config, random] = args;
if (config.elites > 0)
{
@ -70,39 +69,38 @@ namespace blt::gp
}
for (blt::size_t i = 0; i < config.elites; i++)
next_pop.push_back(current_pop.get_individuals()[values[i].first].tree);
next_pop.get_individuals()[i].copy_fast(current_pop.get_individuals()[values[i].first].tree);
}
};
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) {
auto& [program, next_pop, current_pop, current_stats, config, random] = args;
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))
{
// auto state = tracker.start_measurement();
// crossover
auto& p1 = crossover_selection.select(program, current_pop);
auto& p2 = crossover_selection.select(program, current_pop);
auto results = config.crossover.get().apply(program, p1, p2);
// if crossover fails, we can check for mutation on these guys. otherwise straight copy them into the next pop
if (results)
const tree_t* p1;
const tree_t* p2;
do
{
next_pop.push_back(std::move(results->child1));
if (next_pop.size() != config.population_size)
next_pop.push_back(std::move(results->child2));
}
p1 = &crossover_selection.select(program, current_pop);
p2 = &crossover_selection.select(program, current_pop);
} while (!config.crossover.get().apply(program, *p1, *p2, c1, *c2));
// tracker.stop_measurement(state);
// BLT_TRACE("Crossover Allocated %ld times with a total of %s", state.getAllocationDifference(),
// blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str());
return 2;
}
break;
case 1:
@ -110,11 +108,15 @@ namespace blt::gp
{
// auto state = tracker.start_measurement();
// mutation
auto& p = mutation_selection.select(program, current_pop);
next_pop.push_back(std::move(config.mutator.get().apply(program, p)));
const tree_t* p;
do
{
p = &mutation_selection.select(program, current_pop);
} while (!config.mutator.get().apply(program, *p, c1));
// tracker.stop_measurement(state);
// BLT_TRACE("Mutation Allocated %ld times with a total of %s", state.getAllocationDifference(),
// blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str());
return 1;
}
break;
case 2:
@ -122,11 +124,11 @@ namespace blt::gp
{
// auto state = tracker.start_measurement();
// reproduction
auto& p = reproduction_selection.select(program, current_pop);
next_pop.push_back(p);
c1 = reproduction_selection.select(program, current_pop);
// tracker.stop_measurement(state);
// BLT_TRACE("Reproduction Allocated %ld times with a total of %s", state.getAllocationDifference(),
// blt::byte_convert_t(state.getAllocatedByteDifference()).convert_to_nearest_type().to_pretty_string().c_str());
return 1;
}
break;
default:
@ -136,6 +138,7 @@ namespace blt::gp
BLT_UNREACHABLE;
#endif
}
return 0;
};
class selection_t
@ -147,7 +150,7 @@ namespace blt::gp
* @param stats the populations statistics
* @return
*/
virtual tree_t& select(gp_program& program, population_t& pop) = 0;
virtual const tree_t& select(gp_program& program, const population_t& pop) = 0;
virtual void pre_process(gp_program&, population_t&)
{}
@ -158,19 +161,19 @@ namespace blt::gp
class select_best_t : public selection_t
{
public:
tree_t& select(gp_program& program, population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) final;
};
class select_worst_t : public selection_t
{
public:
tree_t& select(gp_program& program, population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) final;
};
class select_random_t : public selection_t
{
public:
tree_t& select(gp_program& program, population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) final;
};
class select_tournament_t : public selection_t
@ -182,7 +185,7 @@ namespace blt::gp
BLT_ABORT("Unable to select with this size. Must select at least 1 individual_t!");
}
tree_t& select(gp_program& program, population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) final;
private:
const blt::size_t selection_size;
@ -191,7 +194,7 @@ namespace blt::gp
class select_fitness_proportionate_t : public selection_t
{
public:
tree_t& select(gp_program& program, population_t& pop) final;
const tree_t& select(gp_program& program, const population_t& pop) final;
};
}

View File

@ -57,16 +57,6 @@ namespace blt::gp
class crossover_t
{
public:
enum class error_t
{
NO_VALID_TYPE,
TREE_TOO_SMALL
};
struct result_t
{
tree_t child1;
tree_t child2;
};
struct crossover_point_t
{
blt::ptrdiff_t p1_crossover_point;
@ -87,7 +77,7 @@ namespace blt::gp
explicit crossover_t(const config_t& config): config(config)
{}
blt::expected<crossover_t::crossover_point_t, error_t> get_crossover_point(gp_program& program, const tree_t& c1, const tree_t& c2) const;
std::optional<crossover_t::crossover_point_t> get_crossover_point(gp_program& program, 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.
@ -97,7 +87,7 @@ namespace blt::gp
* @param p2 reference to the second parent
* @return expected pair of child otherwise returns error enum
*/
virtual blt::expected<result_t, error_t> apply(gp_program& program, const tree_t& p1, const tree_t& p2); // NOLINT
virtual bool apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2); // NOLINT
virtual ~crossover_t() = default;
@ -126,7 +116,7 @@ namespace blt::gp
explicit mutation_t(const config_t& config): config(config)
{}
virtual tree_t apply(gp_program& program, const tree_t& p);
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);
@ -155,7 +145,7 @@ namespace blt::gp
explicit advanced_mutation_t(const config_t& config): mutation_t(config)
{}
tree_t apply(gp_program& program, const tree_t& p) final;
bool apply(gp_program& program, const tree_t& p, tree_t& c) final;
advanced_mutation_t& set_per_node_mutation_chance(double v)
{
@ -170,11 +160,11 @@ namespace blt::gp
double per_node_mutation_chance = 5.0;
static constexpr std::array<double, operators_size> mutation_operator_chances = detail::aggregate_array<operators_size>(
0.1, // EXPRESSION
0.25, // ADJUST
0.25, // EXPRESSION
0.15, // ADJUST
0.01, // SUB_FUNC
0.25, // JUMP_FUNC
0.12 // COPY
0.01, // JUMP_FUNC
0.05 // COPY
);
};

View File

@ -56,6 +56,37 @@ namespace blt::gp
public:
explicit tree_t(gp_program& program);
tree_t(const tree_t& copy) = default;
tree_t& operator=(const tree_t& copy)
{
if (this == &copy)
return *this;
copy_fast(copy);
return *this;
}
/**
* 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.
*/
void copy_fast(const tree_t& copy)
{
if (this == &copy)
return;
values.reserve(copy.values.internal_storage_size());
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;
tree_t& operator=(tree_t&& move) = default;
void clear(gp_program& program);
struct child_t
@ -178,6 +209,14 @@ namespace blt::gp
tree_t tree;
fitness_t fitness;
void copy_fast(const tree_t& copy)
{
// fast copy of the tree
tree.copy_fast(copy);
// reset fitness
fitness = {};
}
individual_t() = delete;
explicit individual_t(tree_t&& tree): tree(std::move(tree))
@ -284,6 +323,11 @@ namespace blt::gp
return individuals;
}
[[nodiscard]] const tracked_vector<individual_t>& get_individuals() const
{
return individuals;
}
population_tree_iterator for_each_tree()
{
return population_tree_iterator{individuals, 0};

View File

@ -21,11 +21,11 @@
namespace blt::gp
{
tree_t& select_best_t::select(gp_program&, population_t& pop)
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;
tree_t* tree = &first.tree;
const tree_t* tree = &first.tree;
for (auto& ind : pop.get_individuals())
{
if (ind.fitness.adjusted_fitness > best_fitness)
@ -37,11 +37,11 @@ namespace blt::gp
return *tree;
}
tree_t& select_worst_t::select(gp_program&, population_t& pop)
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;
tree_t* tree = &first.tree;
const tree_t* tree = &first.tree;
for (auto& ind : pop.get_individuals())
{
if (ind.fitness.adjusted_fitness < worst_fitness)
@ -53,12 +53,12 @@ namespace blt::gp
return *tree;
}
tree_t& select_random_t::select(gp_program& program, population_t& pop)
const tree_t& select_random_t::select(gp_program& program, const population_t& pop)
{
return pop.get_individuals()[program.get_random().get_size_t(0ul, pop.get_individuals().size())].tree;
}
tree_t& select_tournament_t::select(gp_program& program, population_t& pop)
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());
auto& i_ref = pop.get_individuals();
@ -71,7 +71,7 @@ namespace blt::gp
return i_ref[best].tree;
}
tree_t& select_fitness_proportionate_t::select(gp_program& program, population_t& pop)
const tree_t& select_fitness_proportionate_t::select(gp_program& program, const population_t& pop)
{
auto& stats = program.get_population_stats();
auto choice = program.get_random().get_double();

View File

@ -59,23 +59,21 @@ namespace blt::gp
mutation_t::config_t::config_t(): generator(grow_generator)
{}
blt::expected<crossover_t::result_t, crossover_t::error_t> crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2) // NOLINT
bool crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2, tree_t& c1, tree_t& c2) // NOLINT
{
result_t result{p1, p2};
auto& c1 = result.child1;
auto& c2 = result.child2;
c1.copy_fast(p1);
c2.copy_fast(p2);
auto& c1_ops = c1.get_operations();
auto& c2_ops = c2.get_operations();
if (c1_ops.size() < 5 || c2_ops.size() < 5)
return blt::unexpected(error_t::TREE_TOO_SMALL);
return false;
auto point = get_crossover_point(program, c1, c2);
auto point = get_crossover_point(program, p1, p2);
if (!point)
return blt::unexpected(point.error());
return false;
auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point);
@ -104,8 +102,8 @@ namespace blt::gp
blt::size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr);
auto c1_total = static_cast<blt::ptrdiff_t>(c1_stack_after_bytes + c1_stack_for_bytes);
auto c2_total = static_cast<blt::ptrdiff_t>(c2_stack_after_bytes + c2_stack_for_bytes);
auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1>(c1_total);
auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2>(c2_total);
auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
c1_stack.copy_to(copy_ptr_c1, c1_total);
c1_stack.pop_bytes(c1_total);
@ -131,8 +129,8 @@ namespace blt::gp
c2_ops.insert(++insert_point_c2, c1_operators.begin(), c1_operators.end());
#if BLT_DEBUG_LEVEL >= 2
blt::size_t c1_found_bytes = result.child1.get_values().size().total_used_bytes;
blt::size_t c2_found_bytes = result.child2.get_values().size().total_used_bytes;
blt::size_t c1_found_bytes = c1.get_values().size().total_used_bytes;
blt::size_t c2_found_bytes = c2.get_values().size().total_used_bytes;
blt::size_t c1_expected_bytes = std::accumulate(result.child1.get_operations().begin(), result.child1.get_operations().end(), 0ul,
[](const auto& v1, const auto& v2) {
if (v2.is_value)
@ -153,10 +151,10 @@ namespace blt::gp
}
#endif
return result;
return true;
}
blt::expected<crossover_t::crossover_point_t, crossover_t::error_t> crossover_t::get_crossover_point(gp_program& program, const tree_t& c1,
std::optional<crossover_t::crossover_point_t> crossover_t::get_crossover_point(gp_program& program, const tree_t& c1,
const tree_t& c2) const
{
auto& c1_ops = c1.get_operations();
@ -194,10 +192,10 @@ namespace blt::gp
}
}
if (!found)
return blt::unexpected(error_t::NO_VALID_TYPE);
return {};
}
// should we try again over the whole tree? probably not.
return blt::unexpected(error_t::NO_VALID_TYPE);
return {};
} else
{
attempted_point = program.get_random().get_size_t(1ul, c2_ops.size());
@ -213,13 +211,13 @@ namespace blt::gp
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
}
tree_t mutation_t::apply(gp_program& program, const tree_t& p)
bool mutation_t::apply(gp_program& program, const tree_t& p, tree_t& c)
{
auto c = p;
c.copy_fast(p);
mutate_point(program, c, program.get_random().get_size_t(0ul, c.get_operations().size()));
return c;
return true;
}
blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node)
@ -304,10 +302,10 @@ namespace blt::gp
return begin_point + new_ops_r.size();
}
tree_t advanced_mutation_t::apply(gp_program& program, const tree_t& p)
bool advanced_mutation_t::apply(gp_program& program, const tree_t& p, tree_t& c)
{
// child tree
tree_t c = p;
c.copy_fast(p);
auto& ops = c.get_operations();
auto& vals = c.get_values();
@ -732,6 +730,6 @@ namespace blt::gp
}
#endif
return c;
return true;
}
}