pulsatrix
Loading...
Searching...
No Matches
selection.hpp
Go to the documentation of this file.
1
16#pragma once
17
18#include <algorithm>
19#include <numeric>
20#include <random>
21#include <stdexcept>
22#include <vector>
23
24#include "pulsatrix/assert.hpp"
26
27namespace pulsatrix {
28
44template <typename Genotype, typename FitnessT, typename RNG>
45size_t TournamentSelect(const std::vector<Individual<Genotype, FitnessT>>& population,
46 size_t tournament_size, RNG& rng) {
47 if (population.empty()) {
48 throw std::invalid_argument("TournamentSelect: population must not be empty");
49 }
50 if (tournament_size == 0 || tournament_size > population.size()) {
51 throw std::invalid_argument(
52 "TournamentSelect: tournament_size must be in [1, population.size()]");
53 }
54
55 std::vector<size_t> indices(population.size());
56 std::iota(indices.begin(), indices.end(), size_t{0});
57 std::shuffle(indices.begin(), indices.end(), rng);
58
59 size_t best_index = indices[0];
60 for (size_t i = 1; i < tournament_size; ++i) {
61 if (population[indices[i]].fitness > population[best_index].fitness) {
62 best_index = indices[i];
63 }
64 }
65 return best_index;
66}
67
79template <typename Genotype, typename FitnessT>
80size_t RouletteSelectByDraw(const std::vector<Individual<Genotype, FitnessT>>& population,
81 FitnessT draw) {
82 if (population.empty()) {
83 throw std::invalid_argument("RouletteSelectByDraw: population must not be empty");
84 }
85 FitnessT total{};
86 for (const auto& ind : population) {
87 if (ind.fitness < FitnessT{0}) {
88 throw std::invalid_argument("RouletteSelectByDraw: fitness must be non-negative");
89 }
90 total += ind.fitness;
91 }
92 if (!(total > FitnessT{0})) {
93 throw std::invalid_argument("RouletteSelectByDraw: total fitness must be positive");
94 }
95 if (draw < FitnessT{0} || draw >= total) {
96 throw std::invalid_argument("RouletteSelectByDraw: draw must be in [0, total_fitness)");
97 }
98
99 FitnessT cumulative{};
100 for (size_t i = 0; i < population.size(); ++i) {
101 cumulative += population[i].fitness;
102 if (draw < cumulative) {
103 return i;
104 }
105 }
106 // Unreachable: draw < total is enforced above, and cumulative reaches total by the last
107 // element -- an internal invariant, not a condition a caller can trigger.
108 PULSATRIX_ASSERT(false);
109 return population.size() - 1;
110}
111
118template <typename Genotype, typename FitnessT, typename RNG>
119size_t RouletteSelect(const std::vector<Individual<Genotype, FitnessT>>& population, RNG& rng) {
120 FitnessT total{};
121 for (const auto& ind : population) {
122 total += ind.fitness;
123 }
124 std::uniform_real_distribution<FitnessT> dist(FitnessT{0}, total);
125 return RouletteSelectByDraw(population, dist(rng));
126}
127
139template <typename Genotype, typename FitnessT>
140size_t RankSelectByDraw(const std::vector<Individual<Genotype, FitnessT>>& population,
141 double draw) {
142 if (population.empty()) {
143 throw std::invalid_argument("RankSelectByDraw: population must not be empty");
144 }
145
146 std::vector<size_t> order(population.size());
147 std::iota(order.begin(), order.end(), size_t{0});
148 std::sort(order.begin(), order.end(), [&population](size_t a, size_t b) {
149 return population[a].fitness < population[b].fitness;
150 });
151
152 const double n = static_cast<double>(population.size());
153 const double total_weight = n * (n + 1.0) / 2.0; // 1 + 2 + ... + n
154 if (draw < 0.0 || draw >= total_weight) {
155 throw std::invalid_argument("RankSelectByDraw: draw must be in [0, total_weight)");
156 }
157
158 double cumulative = 0.0;
159 for (size_t i = 0; i < order.size(); ++i) {
160 cumulative += static_cast<double>(i + 1); // rank of order[i] is i+1 (1-indexed)
161 if (draw < cumulative) {
162 return order[i];
163 }
164 }
165 // Unreachable: draw < total_weight is enforced above -- an internal invariant.
166 PULSATRIX_ASSERT(false);
167 return order.back();
168}
169
175template <typename Genotype, typename FitnessT, typename RNG>
176size_t RankSelect(const std::vector<Individual<Genotype, FitnessT>>& population, RNG& rng) {
177 const double n = static_cast<double>(population.size());
178 const double total_weight = n * (n + 1.0) / 2.0;
179 std::uniform_real_distribution<double> dist(0.0, total_weight);
180 return RankSelectByDraw(population, dist(rng));
181}
182
183} // namespace pulsatrix
PULSATRIX_ASSERT – debug-only invariant check for programmer errors, distinct from throw (used for ca...
#define PULSATRIX_ASSERT(cond)
Aborts with a diagnostic message if cond is false. Debug-only – use for conditions that indicate a bu...
Definition assert.hpp:22
A genetic-algorithm candidate solution: a genotype paired with its fitness.
Definition acquisition_functions.hpp:16
size_t RouletteSelect(const std::vector< Individual< Genotype, FitnessT > > &population, RNG &rng)
RNG-driven wrapper around RouletteSelectByDraw: draws uniformly from [0, total_fitness) and selects a...
Definition selection.hpp:119
size_t RankSelect(const std::vector< Individual< Genotype, FitnessT > > &population, RNG &rng)
RNG-driven wrapper around RankSelectByDraw: draws uniformly from [0, total_weight) and selects accord...
Definition selection.hpp:176
size_t RankSelectByDraw(const std::vector< Individual< Genotype, FitnessT > > &population, double draw)
Linear-rank selection given an explicit draw in [0, total_weight). Individuals are ranked ascending b...
Definition selection.hpp:140
size_t TournamentSelect(const std::vector< Individual< Genotype, FitnessT > > &population, size_t tournament_size, RNG &rng)
Tournament selection: draw tournament_size individuals without replacement from population and return...
Definition selection.hpp:45
size_t RouletteSelectByDraw(const std::vector< Individual< Genotype, FitnessT > > &population, FitnessT draw)
Fitness-proportionate ("roulette wheel") selection given an explicit draw in [0, total_fitness)....
Definition selection.hpp:80
A single candidate solution in a genetic algorithm population.
Definition individual.hpp:25