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};