From 3a901a0a409ffcb620c65da9faa1994ea2c434bf Mon Sep 17 00:00:00 2001 From: Seth Hamilton Date: Thu, 21 Aug 2025 00:41:51 -0500 Subject: [PATCH] working through compile bugs --- inc/genetic.h | 28 +++++--- src/genetic.cpp | 180 ++++++++++++++++++++++++++++++++++++------------ src/main.cpp | 174 ++++++++++++++++------------------------------ 3 files changed, 215 insertions(+), 167 deletions(-) diff --git a/inc/genetic.h b/inc/genetic.h index 9a32d83..61bae51 100644 --- a/inc/genetic.h +++ b/inc/genetic.h @@ -2,8 +2,7 @@ namespace genetic { -template struct ReadonlySpan; -template struct Span; +template struct Array; template struct Stats; template struct Strategy; @@ -16,7 +15,8 @@ template struct Strategy { // before accessing/locking the work queue again. int num_cells; // Size of the population pool int num_generations; // Number of times (epochs) to run the algorithm - bool test_all; // Sets whether or not every cell is tested every generation + bool test_all; // Sets whether or not every cell's fitness is evaluated every + // generation float test_chance; // Chance to test any given cell's fitness. Relevant only // if test_all is false. bool enable_crossover; // Cells that score well in the evaluation stage @@ -25,11 +25,17 @@ template struct Strategy { float crossover_mutation_chance; // Chance to mutate a child cell int crossover_parent_num; // Number of unique high-scoring parents in a // crossover call. - int crossover_children_num; // Number of children to expect the user to - // produce in the crossover function. - bool enable_mutation; // Cells may be mutated - // before fitness evaluation - float mutation_chance; // Chance to mutate cells before fitness evaluation + int crossover_parent_stride; // Number of parents to skip over when moving to + // the next set of parents. A stride of 1 would + // produce maximum overlap because the set of + // parents would only change by one every + // crossover. + int crossover_children_num; // Number of children to expect the user to + // produce in the crossover function. + bool enable_mutation; // Cells may be mutated + // before fitness evaluation + float mutation_chance; // Chance for any given cell to be mutated cells during + // the mutation uint64_t rand_seed; bool higher_fitness_is_better; // Sets whether or not to consider higher // fitness values better or worse. Set this to @@ -38,16 +44,16 @@ template struct Strategy { // User defined functions T (*make_default_cell)(); void (*mutate)(T &cell_to_modify); - void (*crossover)(const Span parents, const Span out_children); + void (*crossover)(const Array parents, const Array out_children); float (*fitness)(const T &cell); }; template struct Stats { std::vector best_cell; - std::vector average_fitness; + std::vector best_cell_fitness; }; -template struct Span { +template struct Array { T *_data; int len; diff --git a/src/genetic.cpp b/src/genetic.cpp index ab923a4..f89472c 100644 --- a/src/genetic.cpp +++ b/src/genetic.cpp @@ -1,9 +1,14 @@ -#include "genetic.h" -#include "pthread.h" +#include +#include +#include #include #include #include +#include "genetic.h" +#include "pthread.h" +#include "rand.h" + #define NUM_QUEUE_RETRIES 10 using namespace std; @@ -19,23 +24,27 @@ template overload(Ts...) -> overload; namespace genetic { -template struct CellEntry { +template struct cell_entry { float score; T *cell; bool stale; }; -template struct CrossoverJob { - Span *> &parents; - Span *> &children_out; +template struct crossover_job { + Array *> &parents; + Array *> &children_out; }; -template struct FitnessJob { - CellEntry *cell_entry; +template struct fitness_job { + cell_entry *cell_entry; }; -template struct WorkQueue { - variant, FitnessJob> *jobs; +template struct mutate_job { + cell_entry *cell_entry; +}; + +template struct work_queue { + variant, fitness_job, mutate_job> *jobs; int len; int read_i; int write_i; @@ -49,9 +58,9 @@ template struct WorkQueue { pthread_cond_t jobs_available_cond; }; -template WorkQueue make_work_queue(int len) { - return {.jobs = (variant, CrossoverJob> *)malloc( - sizeof(variant, CrossoverJob>) * len), +template work_queue make_work_queue(int len) { + return {.jobs = (variant, crossover_job> *)malloc( + sizeof(variant, crossover_job>) * len), .len = len, .read_i = 0, .write_i = 0, @@ -63,19 +72,19 @@ template WorkQueue make_work_queue(int len) { .jobs_available_cond = PTHREAD_COND_INITIALIZER}; } -template struct JobBatch { - ReadonlySpan, FitnessJob>> jobs; +template struct job_batch { + Array, fitness_job>> jobs; bool gen_complete; }; template -optional> get_job_batch(WorkQueue &queue, int batch_size, - bool *stop_flag) { +optional> get_job_batch(work_queue &queue, int batch_size, + bool *stop_flag) { while (true) { for (int i = 0; i < NUM_QUEUE_RETRIES; i++) { if (queue.read_i < queue.write_i && pthread_mutex_trylock(&queue.data_mutex)) { - JobBatch res; + job_batch res; res.jobs._data = &queue._jobs[queue.read_i]; int span_size = min(batch_size, queue.write_i - queue.read_i); res.jobs.len = span_size; @@ -94,27 +103,41 @@ optional> get_job_batch(WorkQueue &queue, int batch_size, } } -template struct WorkerThreadArgs { +template struct worker_thread_args { Strategy &strat; - WorkQueue &queue; + work_queue &queue; bool *stop_flag; }; -template void do_crossover_job(CrossoverJob cj) {} - template void *worker(void *args) { - WorkerThreadArgs *work_args = (WorkerThreadArgs *)args; + worker_thread_args *work_args = (worker_thread_args *)args; Strategy &strat = work_args->strat; - WorkQueue &queue = work_args->queue; + work_queue &queue = work_args->queue; bool *stop_flag = work_args->stop_flag; - auto JobDispatcher = overload{ - [strat](FitnessJob fj) { - fj.cell_entry->result_out = strat.fitness(*(fj.cell_entry->cell)); - fj.cell_entry->stale = true; + auto job_dispatcher = overload{ + [strat](mutate_job mj) { + strat.mutate(*mj.cell_entry->cell); + mj.cell_entry->stale = true; }, - [strat](CrossoverJob cj) { - strat.crossover(cj.parents, cj.children_out); + [strat](fitness_job fj) { + fj.cell_entry->score = strat.fitness(*fj.cell_entry->cell); + fj.cell_entry->stale = false; + }, + [strat](crossover_job cj) { + Array parent_cells, child_cells; + parent_cells = {(T **)malloc(sizeof(T *) * cj.parents.len), + cj.parents.len}; + child_cells = {(T **)malloc(sizeof(T *) * cj.children_out.len), + cj.children_out.len}; + for (int i = 0; i < cj.parents.len; i++) { + parent_cells[i] = cj.parents[i].cell; + } + for (int i = 0; i < cj.children_out.len; i++) { + child_cells[i] = cj.children_out[i].cell; + cj.children_out[i].stale = true; + } + strat.crossover(parent_cells, child_cells); }, }; @@ -125,7 +148,7 @@ template void *worker(void *args) { // Do the actual work for (int i = 0; i < batch->jobs.len; i++) { - visit(JobDispatcher, batch->jobs[i]); + visit(job_dispatcher, batch->jobs[i]); } if (batch->gen_complete) { @@ -136,20 +159,24 @@ template void *worker(void *args) { template Stats run(Strategy strat) { Stats stats; - WorkQueue work_queue = make_work_queue(strat.num_cells); + // The work queue is what all the worker threads will checking + // for jobs + work_queue queue = make_work_queue(strat.num_cells); + + // The actual cells. Woo! T cells[strat.num_cells]; // Using a vector so I can use the make_heap, push_heap, etc. - vector> cell_queue; + vector> cell_queue; for (int i = 0; i < strat.num_cells; i++) { cells[i] = strat.make_default_cell(); cell_queue.push_back({0, &cells[i], true}); } bool stop_flag = false; - WorkerThreadArgs args = { - .strat = strat, .queue = work_queue, .stop_flag = &stop_flag}; + worker_thread_args args = { + .strat = strat, .queue = queue, .stop_flag = &stop_flag}; // spawn worker threads pthread_t threads[strat.num_threads]; @@ -157,28 +184,95 @@ template Stats run(Strategy strat) { pthread_create(&threads[i], NULL, worker, (void *)args); } - for (int i = 0; i < strat.num_generations; i++) { - // generate fitness jobs - if (strat.test_all) { + uint64_t rand_state = strat.rand_seed; - } else { + for (int i = 0; i < strat.num_generations; i++) { + // Mutate some random cells in the population + for (int i = 0; i < cell_queue.size(); i++) { + if (abs(norm_rand(rand_state)) < strat.mutation_chance) { + queue.jobs[queue.write_i] = mutate_job{&cell_queue[i]}; + queue.write_i++; + } } + pthread_cond_broadcast(&queue.jobs_available_cond); + + // Potential issue here where mutations aren't done computing and fitness + // jobs begin. maybe need to gate this. + + // Generate fitness jobs + for (int i = 0; i < cell_queue.size(); i++) { + if (cell_queue[i].stale && + (strat.test_all || abs(norm_rand(rand_state)) < strat.test_chance)) { + queue.jobs[queue.write_i] = fitness_job{&cell_queue[i]}; + queue.write_i++; + } + pthread_cond_broadcast(&queue.jobs_available_cond); + } + queue.done_writing = true; // wait for fitness jobs to complete - // sort cells on performance + pthread_mutex_lock(&queue.gen_complete_mutex); + + // Before going to sleep, do a quick check to see if the fitness jobs are + // already complete. + pthread_mutex_lock(&queue.data_mutex); + bool already_complete = queue.read_i != queue.write_i; + pthread_mutex_unlock(&queue.data_mutex); + if (already_complete) { + pthread_mutex_unlock(&queue.gen_complete_mutex); + } else { + pthread_cond_wait(&queue.gen_complete_cond, &queue.gen_complete_mutex); + } + + // Sort cells on performance + std::sort(cell_queue.begin(), cell_queue.end(), + [strat](cell_entry a, cell_entry b) { + return strat.higher_fitness_is_better ? a > b : a < b; + }); + + printf("Top Score: %f\n", cell_queue[0].score); + + if (!strat.enable_crossover) + continue; + // generate crossover jobs + // dear god. forgive me father + queue.write_i = 0; + queue.read_i = 0; + int count = 0; + int n_par = strat.crossover_parent_num; + int n_child = strat.crossover_children_num; + int child_i = cell_queue.size() - 1; + int par_i = 0; + while (child_i - par_i <= n_par + n_child) { + Array *> parents = { + (cell_entry **)malloc(sizeof(cell_entry *) * n_par), n_par}; + Array *> children = { + (cell_entry **)malloc(sizeof(cell_entry *) * n_child), n_child}; + + for (; par_i < par_i + n_par; par_i++) { + parents[i] = cell_queue[par_i]; + } + + for (; child_i > child_i - n_child; child_i--) { + children[i] = cell_queue[child_i]; + } + + queue.jobs[queue.write_i] = crossover_job{parents, children}; + par_i += strat.crossover_parent_stride; + child_i += strat.crossover_children_stride; + } } // stop worker threads stop_flag = true; - pthread_cond_broadcast(work_queue.jobs_available_cond); + pthread_cond_broadcast(&queue.jobs_available_cond); for (int i = 0; i < strat.num_threads; i++) { pthread_join(threads[i], NULL); } } -template T &Span::operator[](int i) { - assert(i >= 0 && i < len); +template T &Array::operator[](int i) { return _data[i]; } diff --git a/src/main.cpp b/src/main.cpp index e60847d..7b07dbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,133 +1,81 @@ -#include #include +#include #include -#include -#include +#include "genetic.h" +#include "rand.h" -#define MUTATION_CHANCE 1.0 +using namespace genetic; -float norm_rand() { return (float)rand() / RAND_MAX; } +const int len = 10; +const float max_float = 9999.9f; +static uint64_t seed = 12; +static float num_mutate_chance = 0.5; +static int num_parents = 2; +static int num_children = 2; -enum class ConstraintType { - PRODUCT = 0, - SUM = 1, - INDEX_EQ = 2, -}; -struct Constraint { - ConstraintType type; - int optional_i; - float value; -}; -static std::vector constraints; +static int target_sum = 200; +static int target_product = 300; -struct Cell { - int n; - float *params; -}; - -Cell make_cell(int num_params) { - Cell res = {num_params, (float *)malloc(num_params * sizeof(float))}; - for (int i = 0; i < num_params; i++) { - res.params[i] = 0; - } - return res; +Array make_new_arr() { + Array arr = { (float*)malloc(sizeof(float)*len), len }; + for (int i = 0; i < arr.len; i++) { + arr[i] = norm_rand(seed) * max_float; + } + return arr; } -float get_cell_err(const Cell &a) { - float total_diff = 0; - for (auto c : constraints) { - switch (c.type) { - case ConstraintType::SUM: { - float sum = 0; - for (int i = 0; i < a.n; i++) { - sum += a.params[i]; - } - total_diff += abs(c.value - sum); - break; +void mutate(Array &arr_to_mutate) { + for (int i = 0; i < len; i++) { + if (norm_rand(seed) < num_mutate_chance) { + arr_to_mutate[i] = norm_rand(seed) * max_float; + } } - case ConstraintType::PRODUCT: { - float prod = 1; - for (int i = 0; i < a.n; i++) { - prod *= a.params[i]; - } - total_diff += abs(c.value - prod); - break; - } - case ConstraintType::INDEX_EQ: { - assert(c.optional_i < a.n); - total_diff += abs(c.value - a.params[c.optional_i]); - break; - } - } - } - return total_diff; } -bool operator<(const Cell &a, const Cell &b) { - assert(a.n == b.n); - return get_cell_err(a) < get_cell_err(b); +void crossover(const Array*> parents, const Array *> out_children) { + for (int i = 0; i < len; i++) { + (*out_children._data[0])[i] = i < len/2 ? (*parents._data[0])[i] : (*parents._data[1])[i]; + (*out_children._data[1])[i] = i < len/2 ? (*parents._data[1])[i] : (*parents._data[0])[i]; + } } -void combine_cells(const Cell &a, const Cell &b, Cell *child) { - bool a_first = norm_rand() > 0.5f; - for (int i = 0; i < a.n; i++) { - float offset = norm_rand() * 10; - float roll = norm_rand(); - if (a_first) { - child->params[i] = (i < a.n / 2 ? a.params[i] : b.params[i]) + - (roll > 0.5 ? offset : -offset); - } else { - child->params[i] = (i < a.n / 2 ? b.params[i] : a.params[i]) + - (roll > 0.5 ? offset : -offset); +// norm_rand can go negative. fix in genetic.cpp +// child stride doesn't make sense. Should always skip over child num + +float fitness(const Array &cell) { + float sum = 0; + float product = 1; + for (int i = 0; i < cell.len; i++) { + sum += cell._data[i]; + product *= cell._data[i]; } - } - float r = norm_rand(); - child->params[(int)r * (a.n - 1)] = r * 100.0; + return abs(sum - target_sum) + abs(product - target_product); } int main(int argc, char **argv) { - int num_params, num_cells, num_generations, num_constraints = 0; - std::cin >> num_params >> num_cells >> num_generations >> num_constraints; + Strategy> strat { + .num_threads = 1, + .batch_size = 1, + .num_cells = 10, + .num_generations = 10, + .test_all = true, + .test_chance = 0.0, // doesn't matter + .enable_crossover = true, + .enable_crossover_mutation = true, + .crossover_mutation_chance = 0.6f, + .crossover_parent_num = 2, + .crossover_parent_stride = 1, + .crossover_children_num = 2, + .enable_mutation = true, + .mutation_chance = 0.8, + .rand_seed = seed, + .higher_fitness_is_better = false, + .make_default_cell=make_new_arr, + .mutate=mutate, + .crossover=crossover, + .fitness=fitness + }; - std::cout << num_params << " " << num_cells << " " << num_generations << " " - << num_constraints << std::endl; - - for (int i = 0; i < num_constraints; i++) { - int type_in, optional_i = 0; - float value; - std::cin >> type_in >> value; - ConstraintType type = static_cast(type_in); - if (type == ConstraintType::INDEX_EQ) { - std::cin >> optional_i; - } - constraints.push_back({type, optional_i, value}); - } - - std::vector cells; - for (int i = 0; i < num_cells; i++) { - cells.push_back(make_cell(num_params)); - } - - for (int i = 0; i < num_generations; i++) { - std::sort(cells.begin(), cells.end()); - for (int j = 0; j < num_cells / 2; j++) { - combine_cells(cells[j], cells[j + 1], &cells[num_cells / 2 + j]); - } - if (i % 1000 == 0) { - std::cout << i << "\t" << get_cell_err(cells[0]) << std::endl; - } - } - std::cout << "Final Answer: "; - float sum = 0; - float product = 1; - for (int i = 0; i < cells[0].n; i++) { - std::cout << cells[0].params[i] << " "; - sum += cells[0].params[i]; - product *= cells[0].params[i]; - } - std::cout << std::endl; - - std::cout << "Sum: " << sum << std::endl; - std::cout << "Product: " << product << std::endl; + auto res = run(strat); }