From 327b04d6fce895cf19e18a247655462278abf427 Mon Sep 17 00:00:00 2001 From: KRM7 <70973547+KRM7@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:40:04 +0200 Subject: [PATCH] better permutation crossover implementation - ~10% for position on tsp439 - ~60% for cycle on tsp439 - ~2% for edge on tsp439 --- src/crossover/crossover_impl.hpp | 234 ++++++++++++------------------- src/crossover/neighbour_list.hpp | 139 ++++++++++++++++++ src/crossover/permutation.cpp | 6 - 3 files changed, 229 insertions(+), 150 deletions(-) create mode 100644 src/crossover/neighbour_list.hpp diff --git a/src/crossover/crossover_impl.hpp b/src/crossover/crossover_impl.hpp index a391958d..79aabaa7 100644 --- a/src/crossover/crossover_impl.hpp +++ b/src/crossover/crossover_impl.hpp @@ -1,15 +1,14 @@ /* Copyright (c) 2022 Krisztián Rugási. Subject to the MIT License. */ -#ifndef GA_CROSSOVER_DTL_HPP -#define GA_CROSSOVER_DTL_HPP +#ifndef GAPP_CROSSOVER_DTL_HPP +#define GAPP_CROSSOVER_DTL_HPP +#include "neighbour_list.hpp" #include "../core/candidate.hpp" #include "../utility/small_vector.hpp" #include "../utility/dynamic_bitset.hpp" #include #include -#include -#include #include #include @@ -55,27 +54,19 @@ namespace gapp::crossover::dtl Candidate positionCrossoverImpl(const Candidate& parent1, const Candidate& parent2, std::span indices); - /* Find the indices of genes in the chromosomes chrom1 and chrom2 which belong to odd cycles. Used in the cycle crossover operator. */ + /* Find the indices of genes in the chromosomes chrom1 and chrom2 which belong to odd cycles. */ template std::vector findOddCycleIndices(const Chromosome& chrom1, const Chromosome& chrom2); + /* Find the indices of genes in the chromosomes chrom1 and chrom2 which belong to odd cycles. */ + template + std::vector findOddCycleIndices(const Chromosome& chrom1, const Chromosome& chrom2); + /* Implementation of the cycle crossover for any gene type. */ template CandidatePair cycleCrossoverImpl(const Candidate& parent1, const Candidate& parent2); - /* A list of neighbours for a gene. */ - template - class NeighbourList; - - /* A list of neighbours for an unsigned integer gene. */ - template - class NeighbourList; - - /* Conctruct the neighbour lists of each gene based on 2 chromosomes. The first and last elements are considered neighbours. */ - template, std::vector>, std::unordered_map>>> - R makeNeighbourLists(const Chromosome& chrom1, const Chromosome& chrom2); - /* Implementation of the edge crossover for any gene type, only generates a single child. */ template Candidate edgeCrossoverImpl(const Candidate& parent1, const Candidate& parent2); @@ -100,9 +91,7 @@ namespace gapp::crossover::dtl #include "../utility/algorithm.hpp" #include "../utility/functional.hpp" -#include "../utility/iterators.hpp" #include "../utility/utility.hpp" -#include #include #include @@ -174,6 +163,24 @@ namespace gapp::crossover::dtl } + template + bool isValidIntegerPermutation(const Chromosome& chrom) + { + if (chrom.empty()) return true; + + if (*std::min_element(chrom.begin(), chrom.end()) != 0) return false; + if (*std::max_element(chrom.begin(), chrom.end()) != chrom.size() - 1) return false; + + detail::dynamic_bitset present(chrom.size()); + for (const T& val : chrom) + { + if (present[val]) return false; + present[val] = true; + } + + return true; + } + template Candidate order1CrossoverImpl(const Candidate& parent1, const Candidate& parent2, size_t first, size_t last) { @@ -210,11 +217,10 @@ namespace gapp::crossover::dtl const size_t chrom_len = parent1.chromosome.size(); const size_t range_len = last - first; - /* The genes have to be unique unsigned integers in the range [0, chrom_len). */ GAPP_ASSERT(first <= last && last <= chrom_len); GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - GAPP_ASSERT(*std::min_element(parent1.chromosome.begin(), parent1.chromosome.end()) == 0); - GAPP_ASSERT(*std::max_element(parent1.chromosome.begin(), parent1.chromosome.end()) == chrom_len - 1); + GAPP_ASSERT(isValidIntegerPermutation(parent1.chromosome)); + GAPP_ASSERT(isValidIntegerPermutation(parent2.chromosome)); detail::dynamic_bitset is_direct(chrom_len); for (size_t idx = first; idx != last; idx++) is_direct[parent1.chromosome[idx]] = true; @@ -266,11 +272,10 @@ namespace gapp::crossover::dtl { const size_t chrom_len = parent1.chromosome.size(); - /* The genes have to be unique unsigned integers in the range [0, chrom_len). */ GAPP_ASSERT(first <= last && last <= chrom_len); GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - GAPP_ASSERT(*std::min_element(parent1.chromosome.begin(), parent1.chromosome.end()) == 0); - GAPP_ASSERT(*std::max_element(parent1.chromosome.begin(), parent1.chromosome.end()) == chrom_len - 1); + GAPP_ASSERT(isValidIntegerPermutation(parent1.chromosome)); + GAPP_ASSERT(isValidIntegerPermutation(parent2.chromosome)); detail::dynamic_bitset is_direct(chrom_len); for (size_t idx = first; idx != last; idx++) is_direct[parent1.chromosome[idx]] = true; @@ -317,24 +322,32 @@ namespace gapp::crossover::dtl Candidate positionCrossoverImpl(const Candidate& parent1, const Candidate& parent2, std::span indices) { const size_t chrom_len = parent1.chromosome.size(); - - /* The genes have to be unique unsigned integers in the range [0, chrom_len). */ + GAPP_ASSERT(std::all_of(indices.begin(), indices.end(), detail::between(0_sz, chrom_len - 1))); GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - GAPP_ASSERT(*std::min_element(parent1.chromosome.begin(), parent1.chromosome.end()) == 0); - GAPP_ASSERT(*std::max_element(parent1.chromosome.begin(), parent1.chromosome.end()) == chrom_len - 1); + GAPP_ASSERT(isValidIntegerPermutation(parent1.chromosome)); + GAPP_ASSERT(isValidIntegerPermutation(parent2.chromosome)); detail::dynamic_bitset is_direct(chrom_len); for (size_t idx : indices) is_direct[parent1.chromosome[idx]] = true; + small_vector next_indirect(chrom_len); + for (ptrdiff_t indirect = -1, i = chrom_len - 1; i >= 0; i--) + { + const T gene = parent1.chromosome[i]; + + indirect = is_direct[gene] ? indirect : i; + next_indirect[i] = indirect; + } + Candidate child = parent1; - for (auto child_pos = child.chromosome.begin(); const T& gene : parent2.chromosome) + for (size_t child_pos = 0; T gene : parent2.chromosome) { if (!is_direct[gene]) { - while (is_direct[*child_pos]) ++child_pos; - *child_pos++ = gene; + child_pos = next_indirect[child_pos]; + child.chromosome[child_pos++] = gene; } } @@ -379,137 +392,71 @@ namespace gapp::crossover::dtl return odd_indices; } - template - CandidatePair cycleCrossoverImpl(const Candidate& parent1, const Candidate& parent2) - { - GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - - const auto odd_cycle_idxs = dtl::findOddCycleIndices(parent1.chromosome, parent2.chromosome); - - Candidate child1 = parent1; - Candidate child2 = parent2; - - for (size_t idx : odd_cycle_idxs) - { - using std::swap; - swap(child1.chromosome[idx], child2.chromosome[idx]); - } - - return { std::move(child1), std::move(child2) }; - } - - - template - class NeighbourList : public detail::container_interface> + template + std::vector findOddCycleIndices(const Chromosome& chrom1, const Chromosome& chrom2) { - public: - using value_type = T; - using reference = T&; - using const_reference = const T&; + GAPP_ASSERT(chrom1.size() == chrom2.size()); + GAPP_ASSERT(isValidIntegerPermutation(chrom1)); + GAPP_ASSERT(isValidIntegerPermutation(chrom2)); - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; + const size_t chrom_len = chrom1.size(); - using iterator = typename std::vector::iterator; - using const_iterator = typename std::vector::const_iterator; - using reverse_iterator = typename std::vector::reverse_iterator; - using const_reverse_iterator = typename std::vector::const_reverse_iterator; + std::vector odd_indices; + odd_indices.reserve(chrom_len / 2); - NeighbourList() { neighbours_.reserve(4); } + detail::dynamic_bitset deleted(chrom_len); + size_t num_deleted = 0; - void add(const T& value) + std::vector index_lookup(chrom_len, 0_sz); + for (size_t i = 0; i < chrom_len; i++) { - if (!detail::contains(neighbours_.cbegin(), neighbours_.cend(), value)) - { - neighbours_.push_back(value); - } + index_lookup[chrom1[i]] = i; } - void remove(const T& value) { detail::erase_first_stable(neighbours_, value); } - - constexpr auto begin() noexcept { return neighbours_.begin(); } - constexpr auto end() noexcept { return neighbours_.end(); } - constexpr auto begin() const noexcept { return neighbours_.begin(); } - constexpr auto end() const noexcept { return neighbours_.end(); } + for (bool odd_cycle = false; num_deleted < chrom_len; odd_cycle ^= 1) + { + size_t pos = deleted.find_first(false); + T cycle_start = chrom1[pos]; - private: - small_vector neighbours_; - }; + deleted[pos] = true; + num_deleted++; - template - class NeighbourList : public detail::iterator_interface> - { - public: - void add(T value) - { - GAPP_ASSERT(value != EMPTY); + if (odd_cycle) odd_indices.push_back(pos); - /* Assume that EMPTY values are at the back. */ - for (T& neighbour : neighbours_) + while (chrom2[pos] != cycle_start) { - if (neighbour == value) return; - if (neighbour == EMPTY) - { - neighbour = value; - return; - } - } - GAPP_UNREACHABLE(); - } + pos = index_lookup[chrom2[pos]]; - void remove(T value) - { - GAPP_ASSERT(value != EMPTY); + deleted[pos] = true; + num_deleted++; - for (T& neighbour : neighbours_) - { - neighbour = (neighbour == value) ? EMPTY : neighbour; + if (odd_cycle) odd_indices.push_back(pos); } } - constexpr size_t size() const noexcept - { - return std::count_if(neighbours_.begin(), neighbours_.end(), detail::not_equal_to(EMPTY)); - } - - constexpr bool empty() const noexcept { return size() == 0; } - - constexpr auto begin() noexcept { return neighbours_.begin(); } - constexpr auto end() noexcept { return neighbours_.end(); } - constexpr auto begin() const noexcept { return neighbours_.begin(); } - constexpr auto end() const noexcept { return neighbours_.end(); } - - static constexpr T EMPTY = T(-1); - - private: - std::array neighbours_{ EMPTY, EMPTY, EMPTY, EMPTY }; - }; + return odd_indices; + } - template - R makeNeighbourLists(const Chromosome& chrom1, const Chromosome& chrom2) + template + CandidatePair cycleCrossoverImpl(const Candidate& parent1, const Candidate& parent2) { - GAPP_ASSERT(chrom1.size() == chrom2.size()); + GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - R nb_lists(chrom1.size()); + const auto odd_cycle_idxs = dtl::findOddCycleIndices(parent1.chromosome, parent2.chromosome); - nb_lists[chrom1.front()].add(chrom1[1]); - nb_lists[chrom2.front()].add(chrom2[1]); + Candidate child1 = parent1; + Candidate child2 = parent2; - for (size_t i = 1; i < chrom1.size() - 1; i++) + for (size_t idx : odd_cycle_idxs) { - nb_lists[chrom1[i]].add(chrom1[i - 1]); - nb_lists[chrom1[i]].add(chrom1[i + 1]); - - nb_lists[chrom2[i]].add(chrom2[i - 1]); - nb_lists[chrom2[i]].add(chrom2[i + 1]); + using std::swap; + swap(child1.chromosome[idx], child2.chromosome[idx]); } - nb_lists[chrom1.back()].add(*(chrom1.end() - 2)); - nb_lists[chrom2.back()].add(*(chrom2.end() - 2)); - - return nb_lists; + return { std::move(child1), std::move(child2) }; } + template Candidate edgeCrossoverImpl(const Candidate& parent1, const Candidate& parent2) { @@ -551,10 +498,9 @@ namespace gapp::crossover::dtl { const size_t chrom_len = parent1.chromosome.size(); - /* The genes have to be unique unsigned integers in the range [0, chrom_len). */ GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); - GAPP_ASSERT(*std::min_element(parent1.chromosome.begin(), parent1.chromosome.end()) == 0); - GAPP_ASSERT(*std::max_element(parent1.chromosome.begin(), parent1.chromosome.end()) == chrom_len - 1); + GAPP_ASSERT(isValidIntegerPermutation(parent1.chromosome)); + GAPP_ASSERT(isValidIntegerPermutation(parent2.chromosome)); auto nb_lists = makeNeighbourLists(parent1.chromosome, parent2.chromosome); @@ -567,7 +513,7 @@ namespace gapp::crossover::dtl while (child.chromosome.size() != chrom_len) { T last_gene = child.chromosome.back(); - T next_gene = T(is_used.find_first(false)); + T next_gene = is_used.find_first(false); for (T neighbour : nb_lists[last_gene]) { @@ -588,6 +534,7 @@ namespace gapp::crossover::dtl return child; } + template Candidate pmxCrossoverImpl(const Candidate& parent1, const Candidate& parent2, size_t first, size_t last) { @@ -624,11 +571,10 @@ namespace gapp::crossover::dtl { const size_t chrom_len = parent1.chromosome.size(); - /* The genes have to be unique unsigned integers in the range [0, chrom_len). */ GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size()); GAPP_ASSERT(first <= last && last <= parent1.chromosome.size()); - GAPP_ASSERT(*std::min_element(parent1.chromosome.begin(), parent1.chromosome.end()) == 0); - GAPP_ASSERT(*std::max_element(parent1.chromosome.begin(), parent1.chromosome.end()) == chrom_len - 1); + GAPP_ASSERT(isValidIntegerPermutation(parent1.chromosome)); + GAPP_ASSERT(isValidIntegerPermutation(parent2.chromosome)); Candidate child = parent2; @@ -663,4 +609,4 @@ namespace gapp::crossover::dtl } // namespace gapp::crossover::dtl -#endif // !GA_CROSSOVER_DTL_HPP \ No newline at end of file +#endif // !GAPP_CROSSOVER_DTL_HPP \ No newline at end of file diff --git a/src/crossover/neighbour_list.hpp b/src/crossover/neighbour_list.hpp new file mode 100644 index 00000000..7e492436 --- /dev/null +++ b/src/crossover/neighbour_list.hpp @@ -0,0 +1,139 @@ +/* Copyright (c) 2024 Krisztián Rugási. Subject to the MIT License. */ + +#ifndef GAPP_CROSSOVER_NEIGHBOUR_LIST_HPP +#define GAPP_CROSSOVER_NEIGHBOUR_LIST_HPP + +#include "../core/candidate.hpp" +#include "../utility/algorithm.hpp" +#include "../utility/iterators.hpp" +#include "../utility/small_vector.hpp" +#include "../utility/utility.hpp" +#include +#include +#include +#include +#include + +namespace gapp::crossover::dtl +{ + template + class NeighbourList : public detail::container_interface> + { + public: + using value_type = T; + using reference = T&; + using const_reference = const T&; + + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + using reverse_iterator = typename std::vector::reverse_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + + constexpr auto begin() noexcept { return neighbours_.begin(); } + constexpr auto end() noexcept { return neighbours_.end(); } + constexpr auto begin() const noexcept { return neighbours_.begin(); } + constexpr auto end() const noexcept { return neighbours_.end(); } + + constexpr void add(const T& value) + { + if (detail::contains(neighbours_.begin(), neighbours_.end(), value)) return; + neighbours_.push_back(value); + } + + constexpr void remove(const T& value) + { + detail::erase_first_stable(neighbours_, value); + } + + private: + small_vector neighbours_; + }; + + template + class NeighbourList : public detail::iterator_interface> + { + public: + constexpr auto begin() noexcept { return neighbours_.begin(); } + constexpr auto end() noexcept { return neighbours_.end(); } + constexpr auto begin() const noexcept { return neighbours_.begin(); } + constexpr auto end() const noexcept { return neighbours_.end(); } + + constexpr size_t size() const noexcept { return size_; } + constexpr bool empty() const noexcept { return size() == 0; } + + void add(T value) + { + GAPP_ASSERT(value != EMPTY); + + /* Assume that EMPTY values are at the back. */ + for (T& neighbour : neighbours_) + { + if (neighbour == value) return; + if (neighbour == EMPTY) + { + neighbour = value; + size_++; + return; + } + } + GAPP_UNREACHABLE(); + } + + void remove(T value) + { + GAPP_ASSERT(value != EMPTY); + + for (T& neighbour : neighbours_) + { + if (neighbour == value) + { + neighbour = EMPTY; + size_--; + } + } + } + + static constexpr T EMPTY = T(-1); + + private: + std::array neighbours_{ EMPTY, EMPTY, EMPTY, EMPTY }; + size_t size_ = 0; + }; + + + template + using NeighbourLists = + std::conditional_t, std::vector>, std::unordered_map>>; + + + template + NeighbourLists makeNeighbourLists(const Chromosome& chrom1, const Chromosome& chrom2) + { + GAPP_ASSERT(chrom1.size() == chrom2.size()); + + NeighbourLists nb_lists(chrom1.size()); + + nb_lists[chrom1.front()].add(chrom1[1]); + nb_lists[chrom2.front()].add(chrom2[1]); + + for (size_t i = 1; i < chrom1.size() - 1; i++) + { + nb_lists[chrom1[i]].add(chrom1[i - 1]); + nb_lists[chrom1[i]].add(chrom1[i + 1]); + + nb_lists[chrom2[i]].add(chrom2[i - 1]); + nb_lists[chrom2[i]].add(chrom2[i + 1]); + } + + nb_lists[chrom1.back()].add(*(chrom1.end() - 2)); + nb_lists[chrom2.back()].add(*(chrom2.end() - 2)); + + return nb_lists; + } + +} // namespace gapp::crossover::dtl + +#endif // !GAPP_CROSSOVER_NEIGHBOUR_LIST_HPP diff --git a/src/crossover/permutation.cpp b/src/crossover/permutation.cpp index 05486dad..a8fd03ff 100644 --- a/src/crossover/permutation.cpp +++ b/src/crossover/permutation.cpp @@ -15,7 +15,6 @@ namespace gapp::crossover::perm auto Order1::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size(); @@ -34,7 +33,6 @@ namespace gapp::crossover::perm auto Order2::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size(); @@ -53,7 +51,6 @@ namespace gapp::crossover::perm auto Position::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size(); @@ -71,7 +68,6 @@ namespace gapp::crossover::perm auto Cycle::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size(); @@ -83,7 +79,6 @@ namespace gapp::crossover::perm auto Edge::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size(); @@ -98,7 +93,6 @@ namespace gapp::crossover::perm auto PMX::crossover(const GA&, const Candidate& parent1, const Candidate& parent2) const -> CandidatePair { GAPP_ASSERT(parent1.chromosome.size() == parent2.chromosome.size(), "Mismatching parent chromosome lengths."); - /* The genes of the parent chromosomes must be unique. */ const size_t chrom_len = parent1.chromosome.size();