From 7ef544a28e30b697852ec70acbfc044b5e39b22b Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 28 Jan 2025 23:42:56 -0500 Subject: [PATCH] sort of works --- .idea/editor.xml | 717 ++++++++++++------------------------ .idea/vcs.xml | 3 - CMakeLists.txt | 2 +- include/genetic_algorithm.h | 15 +- include/skyscrapers.h | 4 + src/genetic_algorithm.cpp | 121 +++++- src/main.cpp | 19 +- src/skyscrapers.cpp | 47 ++- 8 files changed, 429 insertions(+), 499 deletions(-) diff --git a/.idea/editor.xml b/.idea/editor.xml index 55d1bc1..822bae1 100644 --- a/.idea/editor.xml +++ b/.idea/editor.xml @@ -1,483 +1,244 @@ - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index a7937dc..4ec20c6 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,9 +2,6 @@ - - - diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fcd642..71b8b92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ macro(blt_add_project name source type) project(skyscrapers-ga) endmacro() -project(skyscrapers-ga VERSION 0.0.5) +project(skyscrapers-ga VERSION 0.0.6) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) diff --git a/include/genetic_algorithm.h b/include/genetic_algorithm.h index 1ef0e82..ec9db60 100644 --- a/include/genetic_algorithm.h +++ b/include/genetic_algorithm.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace sky { @@ -55,13 +56,19 @@ namespace sky } } - void run_step(); + void run_step(blt::i32 elites = 2, blt::i32 k = 5); - [[nodiscard]] solution_t select(blt::i32 k = 5) const; + [[nodiscard]] double average_fitness() const; - static std::pair crossover(const individual_t& first, const individual_t& second); + [[nodiscard]] std::vector get_best(blt::i32 amount); - static solution_t mutate(const individual_t& individual); + [[nodiscard]] blt::random::random_t& get_random() const; + + [[nodiscard]] const solution_t& select(blt::i32 k = 5) const; + + [[nodiscard]] std::pair crossover(solution_t first, solution_t second) const; + + [[nodiscard]] solution_t mutate(solution_t individual) const; private: double crossover_rate, mutation_rate; diff --git a/include/skyscrapers.h b/include/skyscrapers.h index ce3d55a..bc01bf3 100644 --- a/include/skyscrapers.h +++ b/include/skyscrapers.h @@ -87,6 +87,10 @@ namespace sky { return board_data[row * board_size + column]; } + + void print() const; + + void print(const problem_t& problem) const; }; problem_t make_test_problem(); diff --git a/src/genetic_algorithm.cpp b/src/genetic_algorithm.cpp index f97842d..495c6dd 100644 --- a/src/genetic_algorithm.cpp +++ b/src/genetic_algorithm.cpp @@ -17,18 +17,93 @@ */ #include #include +#include #include -namespace sky { - void genetic_algorithm::run_step() +namespace sky +{ + void genetic_algorithm::run_step(const blt::i32 elites, const blt::i32 k) { + std::vector next_generation; + const double total_chance = crossover_rate + mutation_rate; + const double adjusted_crossover = crossover_rate / total_chance; + + if (elites > 0) + { + std::sort(individuals.begin(), individuals.end(), [](const auto& a, const auto& b) + { + return a.fitness < b.fitness; + }); + for (blt::i32 i = 0; i < elites; ++i) + next_generation.push_back(individuals[i]); + } + + for (blt::size_t i = 0; i < individuals.size(); i++) + { + if (get_random().choice(adjusted_crossover)) + { + const solution_t& p1 = select(k); + const solution_t* p2; + do + { + p2 = &select(k); + } + while (p2 == &p1); + + auto [c1, c2] = crossover(p1, *p2); + next_generation.emplace_back(c1, c1.fitness(m_problem)); + if (next_generation.size() + 1 < individuals.size()) + { + next_generation.emplace_back(c2, c2.fitness(m_problem)); + ++i; + } + } + else + { + const solution_t& p1 = select(k); + auto c1 = mutate(p1); + next_generation.emplace_back(c1, c1.fitness(m_problem)); + } + } + + individuals.clear(); + individuals = std::move(next_generation); } - solution_t genetic_algorithm::select(const blt::i32 k) const + double genetic_algorithm::average_fitness() const + { + double total_fitness = 0.0; + + for (const auto& i : individuals) + total_fitness += i.fitness; + + return total_fitness / static_cast(individuals.size()); + } + + std::vector genetic_algorithm::get_best(const blt::i32 amount) + { + std::sort(individuals.begin(), individuals.end(), [](const auto& a, const auto& b) + { + return a.fitness < b.fitness; + }); + + std::vector best; + for (blt::size_t i = 0; i < static_cast(amount); i++) + best.push_back(individuals[i]); + + return best; + } + + blt::random::random_t& genetic_algorithm::get_random() const // NOLINT + { + thread_local blt::random::random_t random{std::random_device{}()}; + return random; + } + + const solution_t& genetic_algorithm::select(const blt::i32 k) const { thread_local blt::hashset_t selected_indexes; - thread_local blt::random::random_t random{std::random_device{}()}; selected_indexes.clear(); blt::size_t index = 0; @@ -39,8 +114,9 @@ namespace sky { blt::size_t point; do { - point = random.get_u64(0, individuals.size()); - } while (selected_indexes.contains(point)); + point = get_random().get_u64(0, individuals.size()); + } + while (selected_indexes.contains(point)); selected_indexes.insert(point); if (individuals[point].fitness < best_fitness) { @@ -51,11 +127,40 @@ namespace sky { return individuals[index].solution; } - std::pair genetic_algorithm::crossover(const individual_t& first, const individual_t& second) + std::pair genetic_algorithm::crossover(solution_t first, solution_t second) const { + auto& random = get_random(); + + const auto first_begin = random.get_size_t(0, first.board_size * first.board_size - 1); + const auto first_end = random.get_size_t(first_begin + 1, first.board_size * first.board_size); + + const auto size = first_end - first_begin; + + const auto second_begin = random.get_size_t(0, second.board_size * second.board_size - size); + + std::vector temp; + temp.resize(size); + + std::memcpy(temp.data(), second.board_data.data() + second_begin, sizeof(blt::i32) * size); + std::memcpy(second.board_data.data() + second_begin, first.board_data.data() + first_begin, sizeof(blt::i32) * size); + std::memcpy(first.board_data.data() + first_begin, temp.data(), sizeof(blt::i32) * size); + + return {first, second}; } - solution_t genetic_algorithm::mutate(const individual_t& individual) + solution_t genetic_algorithm::mutate(solution_t individual) const { + auto& random = get_random(); + + // TODO: better mutation + const blt::i32 points = random.get_i32(0, 5); + for (blt::i32 i = 0; i < points; ++i) + { + const auto index = random.get_size_t(0, individual.board_size * individual.board_size); + const auto replacement = random.get_i32(m_problem.min(), m_problem.max() + 1); + individual.board_data[index] = replacement; + } + + return individual; } } diff --git a/src/main.cpp b/src/main.cpp index 66f06ff..819de65 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include #include "blt/gfx/renderer/resource_manager.h" #include "blt/gfx/renderer/batch_2d_renderer.h" @@ -64,9 +65,25 @@ int main(int argc, const char** argv) return EXIT_FAILURE; } - auto& problem_d = problem.value(); + const auto& problem_d = problem.value(); problem_d.print(); + sky::genetic_algorithm ga{problem_d, 500, 0.8, 0.1}; + + for (blt::i32 i = 0; i < 50; i++) + { + ga.run_step(2, 5); + BLT_TRACE("Ran GP generation %d with average fitness %lf", i, ga.average_fitness()); + auto best = ga.get_best(1); + BLT_TRACE("Best individual has fitness: %d", best.front().fitness); + } + + auto best = ga.get_best(1); + BLT_TRACE("Best individual: %d", best.front().solution.fitness(problem_d)); + best.front().solution.print(problem_d); + + BLT_TRACE("----------"); + const auto test = sky::make_test_problem(); auto sol = sky::make_test_solution(); diff --git a/src/skyscrapers.cpp b/src/skyscrapers.cpp index 003f0a3..3e1b50c 100644 --- a/src/skyscrapers.cpp +++ b/src/skyscrapers.cpp @@ -66,7 +66,9 @@ namespace sky return blt::unexpected(problem_t::error_t::MISSING_BOARD_DATA); } - auto top_problems = blt::string::split(lines[1], '\t'); + blt::i32 index = 1; + + auto top_problems = blt::string::split(lines[index++], '\t'); if (top_problems.size() != static_cast(problem.board_size)) { @@ -79,14 +81,13 @@ namespace sky for (blt::size_t i = 0; i < static_cast(problem.board_size); i++) { - const auto index = i + 2; if (index >= lines.size()) { BLT_WARN("File is incorrectly formatted. Expected BOARD_SIZE '%d' number of rows describing the sizes of the board but got %lu", problem.board_size, lines.size()); return blt::unexpected(problem_t::error_t::INCORRECT_BOARD_DATA_FOR_SIZE); } - auto data = blt::string::split(lines[index], '\t'); + auto data = blt::string::split(lines[index++], '\t'); if (data.size() != 2) { BLT_WARN("File is incorrectly formatted. Expected 2 points for the side data descriptors, got %lu", data.size()); @@ -96,7 +97,7 @@ namespace sky problem.right.push_back(std::stoi(data[1])); } - auto bottom_problems = blt::string::split(lines[8], '\t'); + auto bottom_problems = blt::string::split(lines[index], '\t'); if (bottom_problems.size() != static_cast(problem.board_size)) { @@ -226,6 +227,44 @@ namespace sky return fitness; } + void solution_t::print() const + { + for (blt::i32 i = 0; i < board_size; i++) + { + for (blt::i32 j = 0; j < board_size; j++) + { + BLT_TRACE_STREAM << get(i, j); + if (j < board_size - 1) + BLT_TRACE_STREAM << '\t'; + } + BLT_TRACE_STREAM << '\n'; + } + } + + void solution_t::print(const problem_t& problem) const + { + BLT_TRACE("Board Size: %d", board_size); + BLT_TRACE_STREAM << "\t"; + for (int i = 0; i < board_size; i++) + BLT_TRACE_STREAM << problem.top[i] << '\t'; + BLT_TRACE_STREAM << "\n"; + for (int i = 0; i < board_size; i++) + { + BLT_TRACE_STREAM << problem.left[i]; + BLT_TRACE_STREAM << '\t'; + for (int j = 0; j < board_size; j++) + { + BLT_TRACE_STREAM << get(i, j); + BLT_TRACE_STREAM << '\t'; + } + BLT_TRACE_STREAM << problem.right[i] << "\n"; + } + BLT_TRACE_STREAM << "\t"; + for (int i = 0; i < board_size; i++) + BLT_TRACE_STREAM << problem.bottom[i] << '\t'; + BLT_TRACE_STREAM << "\n"; + } + problem_t make_test_problem() { problem_t problem{3};