diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 3739d1a..a7937dc 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,21 +2,20 @@ + + + - - - - diff --git a/CMakeLists.txt b/CMakeLists.txt index f16d71d..8fcd642 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.4) +project(skyscrapers-ga VERSION 0.0.5) 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 new file mode 100644 index 0000000..1ef0e82 --- /dev/null +++ b/include/genetic_algorithm.h @@ -0,0 +1,73 @@ +#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 . + */ + +#ifndef GENETIC_ALGORITHM_H +#define GENETIC_ALGORITHM_H + +#include +#include +#include + +namespace sky +{ + struct individual_t + { + solution_t solution; + blt::i32 fitness = std::numeric_limits::max(); + + individual_t(solution_t solution, const blt::i32 fitness): solution(std::move(solution)), fitness(fitness) + { + } + + void replace(const problem_t& problem, const solution_t& new_solution) + { + solution = new_solution; + fitness = solution.fitness(problem); + } + }; + + class genetic_algorithm + { + public: + genetic_algorithm(problem_t problem, const blt::i32 individual_count, const double crossover_rate = 0.8, const double mutation_rate = 0.1): + crossover_rate(crossover_rate), mutation_rate(mutation_rate), m_problem(std::move(problem)) + { + for (blt::i32 i = 0; i < individual_count; i++) + { + solution_t solution{problem.board_size}; + solution.init(problem); + individuals.emplace_back(solution, solution.fitness(m_problem)); + } + } + + void run_step(); + + [[nodiscard]] solution_t select(blt::i32 k = 5) const; + + static std::pair crossover(const individual_t& first, const individual_t& second); + + static solution_t mutate(const individual_t& individual); + + private: + double crossover_rate, mutation_rate; + problem_t m_problem; + std::vector individuals; + }; +} + +#endif //GENETIC_ALGORITHM_H diff --git a/include/skyscrapers.h b/include/skyscrapers.h index a81fd03..ce3d55a 100644 --- a/include/skyscrapers.h +++ b/include/skyscrapers.h @@ -47,7 +47,7 @@ namespace sky right.reserve(board_size); } - void print(); + void print() const; [[nodiscard]] blt::i32 min() const // NOLINT { @@ -56,11 +56,45 @@ namespace sky [[nodiscard]] blt::i32 max() const { - return board_size + 1; + return board_size; } }; blt::expected problem_from_file(std::string_view path); + + struct solution_t + { + blt::i32 board_size; + std::vector board_data; + + explicit solution_t(const blt::i32 board_size): board_size(board_size) + { + board_data.resize(board_size * board_size); + } + + void init(const problem_t& problem); + + // checks to see if the row contains duplicates. zero means all good + [[nodiscard]] blt::i32 row_incorrect_count(blt::i32 row) const; + // checks to see if the arrows are correct for this row + [[nodiscard]] blt::i32 row_view_count(const problem_t& problem, blt::i32 row) const; + [[nodiscard]] blt::i32 column_incorrect_count(blt::i32 column) const; + [[nodiscard]] blt::i32 column_view_count(const problem_t& problem, blt::i32 column) const; + + [[nodiscard]] blt::i32 fitness(const problem_t& problem) const; + + [[nodiscard]] blt::i32 get(const blt::i32 row, const blt::i32 column) const + { + return board_data[row * board_size + column]; + } + }; + + problem_t make_test_problem(); + + solution_t make_test_solution(); + solution_t make_test_solution_bad1(); + solution_t make_test_solution_bad2(); + solution_t make_test_solution_bad3(); } #endif //SKYSCRAPERS_H diff --git a/skyscraper3x3.txt b/skyscraper3x3.txt new file mode 100644 index 0000000..824415c --- /dev/null +++ b/skyscraper3x3.txt @@ -0,0 +1,7 @@ +BOARD_SIZE: 3 + +2 1 2 +2 2 +3 1 +1 2 +1 3 2 diff --git a/skyscraper_problem.txt b/skyscraper6x6.txt similarity index 100% rename from skyscraper_problem.txt rename to skyscraper6x6.txt diff --git a/src/genetic_algorithm.cpp b/src/genetic_algorithm.cpp new file mode 100644 index 0000000..f97842d --- /dev/null +++ b/src/genetic_algorithm.cpp @@ -0,0 +1,61 @@ +/* + * + * 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 . + */ +#include +#include +#include + +namespace sky { + void genetic_algorithm::run_step() + { + + } + + 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; + blt::i32 best_fitness = std::numeric_limits::max(); + + for (blt::i32 i = 0; i < k; ++i) + { + blt::size_t point; + do + { + point = random.get_u64(0, individuals.size()); + } while (selected_indexes.contains(point)); + selected_indexes.insert(point); + if (individuals[point].fitness < best_fitness) + { + index = point; + best_fitness = individuals[point].fitness; + } + } + return individuals[index].solution; + } + + std::pair genetic_algorithm::crossover(const individual_t& first, const individual_t& second) + { + } + + solution_t genetic_algorithm::mutate(const individual_t& individual) + { + } +} diff --git a/src/main.cpp b/src/main.cpp index aabf5d1..66f06ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,5 +67,16 @@ int main(int argc, const char** argv) auto& problem_d = problem.value(); problem_d.print(); - blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, destroy}.setSyncInterval(1)); + const auto test = sky::make_test_problem(); + auto sol = sky::make_test_solution(); + + test.print(); + BLT_TRACE(sol.fitness(test)); + + BLT_TRACE(sky::make_test_solution_bad1().fitness(test)); + BLT_TRACE(sky::make_test_solution_bad2().fitness(test)); + BLT_TRACE(sky::make_test_solution_bad3().fitness(test)); + + + // blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, destroy}.setSyncInterval(1)); } \ No newline at end of file diff --git a/src/skyscrapers.cpp b/src/skyscrapers.cpp index 2262eb0..003f0a3 100644 --- a/src/skyscrapers.cpp +++ b/src/skyscrapers.cpp @@ -18,11 +18,13 @@ #include #include +#include #include +#include namespace sky { - void problem_t::print() + void problem_t::print() const { BLT_TRACE("Board Size: %d", board_size); BLT_TRACE_STREAM << "\t"; @@ -107,4 +109,183 @@ namespace sky return problem; } + + void solution_t::init(const problem_t& problem) + { + blt::random::random_t random{std::random_device{}()}; + for (auto& v : board_data) + v = random.get_i32(problem.min(), problem.max() + 1); + } + + blt::i32 solution_t::row_incorrect_count(const blt::i32 row) const + { + thread_local std::vector value_counts; + value_counts.resize(board_size); + std::memset(value_counts.data(), 0, sizeof(blt::i32) * board_size); + + for (blt::i32 column = 0; column < board_size; column++) + ++value_counts[get(row, column) - 1]; + + blt::i32 sum = 0; + for (const auto v : value_counts) + sum += std::abs(v - 1); + + return sum; + } + + blt::i32 solution_t::row_view_count(const problem_t& problem, const blt::i32 row) const + { + blt::i32 sees_left = 0; + blt::i32 sees_right = 0; + + blt::i32 highest_left = 0; + blt::i32 highest_right = 0; + + for (blt::i32 column = 0; column < board_size; column++) + { + if (get(row, column) > highest_left) + { + ++sees_left; + highest_left = get(row, column); + } + } + + for (blt::i32 column = board_size - 1; column >= 0; column--) + { + if (get(row, column) > highest_right) + { + ++sees_right; + highest_right = get(row, column); + } + } + + const auto left = problem.left[row]; + const auto right = problem.right[row]; + + return std::abs(left - sees_left) + std::abs(right - sees_right); + } + + blt::i32 solution_t::column_view_count(const problem_t& problem, blt::i32 column) const + { + blt::i32 sees_top = 0; + blt::i32 sees_bottom = 0; + + blt::i32 highest_top = 0; + blt::i32 highest_bottom = 0; + + for (blt::i32 row = 0; row < board_size; row++) + { + if (get(row, column) > highest_top) + { + ++sees_top; + highest_top = get(row, column); + } + } + + for (blt::i32 row = board_size - 1; row >= 0; row--) + { + if (get(row, column) > highest_bottom) + { + ++sees_bottom; + highest_bottom = get(row, column); + } + } + + const auto top = problem.top[column]; + const auto bottom = problem.bottom[column]; + + return std::abs(top - sees_top) + std::abs(bottom - sees_bottom); + } + + blt::i32 solution_t::column_incorrect_count(const blt::i32 column) const + { + thread_local std::vector value_counts; + value_counts.resize(board_size); + std::memset(value_counts.data(), 0, sizeof(blt::i32) * board_size); + + for (blt::i32 row = 0; row < board_size; row++) + ++value_counts[get(row, column) - 1]; + + blt::i32 sum = 0; + for (const auto v : value_counts) + sum += std::abs(v - 1); + + return sum; + } + + blt::i32 solution_t::fitness(const problem_t& problem) const + { + blt::i32 fitness = 0; + for (blt::i32 i = 0; i < board_size; i++) + { + fitness += row_incorrect_count(i); + fitness += row_view_count(problem, i); + fitness += column_incorrect_count(i); + fitness += column_view_count(problem, i); + } + return fitness; + } + + problem_t make_test_problem() + { + problem_t problem{3}; + problem.top = {2, 1, 2}; + problem.bottom = {1, 3, 2}; + problem.left = {2, 3, 1}; + problem.right = {2, 1, 2}; + + return problem; + } + + solution_t make_test_solution() + { + solution_t solution{3}; + + solution.board_data = { + 2, 3, 1, + 1, 2, 3, + 3, 1, 2 + }; + + return solution; + } + + solution_t make_test_solution_bad1() + { + solution_t solution{3}; + + solution.board_data = { + 2, 3, 1, + 1, 2, 3, + 1, 1, 2 + }; + + return solution; + } + + solution_t make_test_solution_bad2() + { + solution_t solution{3}; + + solution.board_data = { + 1, 3, 2, + 1, 2, 3, + 3, 1, 2 + }; + + return solution; + } + + solution_t make_test_solution_bad3() + { + solution_t solution{3}; + + solution.board_data = { + 3, 2, 1, + 1, 3, 2, + 3, 2, 1 + }; + + return solution; + } }