skyscrapers-ga/src/genetic_algorithm.cpp

167 lines
5.5 KiB
C++

/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include <genetic_algorithm.h>
#include <blt/std/hashmap.h>
#include <blt/std/logging.h>
#include <blt/std/random.h>
namespace sky
{
void genetic_algorithm::run_step(const blt::i32 elites, const blt::i32 k)
{
std::vector<individual_t> 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);
}
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<double>(individuals.size());
}
std::vector<individual_t> 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<individual_t> best;
for (blt::size_t i = 0; i < static_cast<blt::size_t>(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<blt::size_t> selected_indexes;
selected_indexes.clear();
blt::size_t index = 0;
blt::i32 best_fitness = std::numeric_limits<blt::i32>::max();
for (blt::i32 i = 0; i < k; ++i)
{
blt::size_t point;
do
{
point = get_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<solution_t, solution_t> 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<blt::i32> 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(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;
}
}