pulsatrix
Loading...
Searching...
No Matches
neat_evolution.hpp
Go to the documentation of this file.
1
12#pragma once
13
14#include <algorithm>
15#include <cmath>
16#include <limits>
17#include <numeric>
18#include <random>
19#include <stdexcept>
20#include <vector>
21
24
25namespace pulsatrix {
26
37inline std::vector<int> AllocateOffspringCounts(const std::vector<double>& species_adjusted_fitness_sums,
38 int population_size) {
39 if (species_adjusted_fitness_sums.empty()) {
40 throw std::invalid_argument("AllocateOffspringCounts: species_adjusted_fitness_sums must not be empty");
41 }
42 if (population_size <= 0) {
43 throw std::invalid_argument("AllocateOffspringCounts: population_size must be positive");
44 }
45
46 size_t n = species_adjusted_fitness_sums.size();
47 double total = std::accumulate(species_adjusted_fitness_sums.begin(), species_adjusted_fitness_sums.end(), 0.0);
48
49 std::vector<int> counts(n, 0);
50 if (total <= 0.0) {
51 int base = population_size / static_cast<int>(n);
52 int remainder = population_size - base * static_cast<int>(n);
53 for (size_t i = 0; i < n; ++i) {
54 counts[i] = base + (static_cast<int>(i) < remainder ? 1 : 0);
55 }
56 return counts;
57 }
58
59 std::vector<double> exact(n);
60 int allocated = 0;
61 for (size_t i = 0; i < n; ++i) {
62 exact[i] = species_adjusted_fitness_sums[i] / total * static_cast<double>(population_size);
63 counts[i] = static_cast<int>(std::floor(exact[i]));
64 allocated += counts[i];
65 }
66
67 int remaining = population_size - allocated;
68 std::vector<size_t> order(n);
69 std::iota(order.begin(), order.end(), 0);
70 std::stable_sort(order.begin(), order.end(), [&](size_t a, size_t b) {
71 return (exact[a] - std::floor(exact[a])) > (exact[b] - std::floor(exact[b]));
72 });
73 for (int i = 0; i < remaining; ++i) {
74 counts[order[i]] += 1;
75 }
76 return counts;
77}
78
84template <typename RNG>
86 double weight_mutation_sigma, double weight_mutation_probability,
87 double add_connection_probability, double add_node_probability) {
88 NEATGenome child = parent;
89 child.MutateWeights(weight_mutation_sigma, weight_mutation_probability, rng);
90 std::bernoulli_distribution add_connection_roll(add_connection_probability);
91 if (add_connection_roll(rng)) {
92 child.AddConnection(tracker, rng);
93 }
94 std::bernoulli_distribution add_node_roll(add_node_probability);
95 if (add_node_roll(rng)) {
96 child.AddNode(tracker, rng);
97 }
98 return child;
99}
100
107
118template <typename FitnessFn, typename RNG>
119NEATEvolutionResult RunNEATEvolution(std::vector<NEATGenome> population, FitnessFn fitness_fn, int num_generations,
120 double compatibility_threshold, double c1, double c2, double c3,
121 double weight_mutation_sigma, double weight_mutation_probability,
122 double add_connection_probability, double add_node_probability,
123 InnovationTracker& tracker, RNG& rng) {
124 if (population.empty()) {
125 throw std::invalid_argument("RunNEATEvolution: population must not be empty");
126 }
127 if (num_generations <= 0) {
128 throw std::invalid_argument("RunNEATEvolution: num_generations must be positive");
129 }
130
131 NEATGenome best_genome = population[0];
132 double best_fitness = -std::numeric_limits<double>::infinity();
133
134 for (int gen = 0; gen < num_generations; ++gen) {
135 std::vector<double> fitnesses(population.size());
136 for (size_t i = 0; i < population.size(); ++i) {
137 fitnesses[i] = fitness_fn(population[i]);
138 if (fitnesses[i] > best_fitness) {
139 best_fitness = fitnesses[i];
140 best_genome = population[i];
141 }
142 }
143
144 if (gen == num_generations - 1) {
145 break;
146 }
147
148 SpeciesAssignment assignment = SpeciatePopulation(population, compatibility_threshold, c1, c2, c3);
149 std::vector<double> adjusted = ComputeAdjustedFitness(fitnesses, assignment.species);
150
151 std::vector<double> species_sums(assignment.species.size(), 0.0);
152 for (size_t s = 0; s < assignment.species.size(); ++s) {
153 for (size_t idx : assignment.species[s]) {
154 species_sums[s] += adjusted[idx];
155 }
156 }
157 std::vector<int> counts = AllocateOffspringCounts(species_sums, static_cast<int>(population.size()));
158
159 std::vector<NEATGenome> next_population;
160 next_population.reserve(population.size());
161 for (size_t s = 0; s < assignment.species.size(); ++s) {
162 int count = counts[s];
163 if (count <= 0) {
164 continue;
165 }
166 std::vector<size_t> members = assignment.species[s];
167 std::sort(members.begin(), members.end(),
168 [&](size_t a, size_t b) { return fitnesses[a] > fitnesses[b]; });
169
170 next_population.push_back(population[members.front()]);
171 std::uniform_int_distribution<size_t> pick_parent(0, members.size() - 1);
172 for (int offspring_index = 1; offspring_index < count; ++offspring_index) {
173 const NEATGenome& parent = population[members[pick_parent(rng)]];
174 next_population.push_back(ReproduceOffspring(parent, tracker, rng, weight_mutation_sigma,
175 weight_mutation_probability,
176 add_connection_probability, add_node_probability));
177 }
178 }
179
180 population = std::move(next_population);
181 }
182
183 return NEATEvolutionResult{best_genome, best_fitness, num_generations};
184}
185
186} // namespace pulsatrix
The global historical-marking registry: the same structural mutation (an identical new connection,...
Definition neat_genome.hpp:55
A NEAT genome: its node and connection genes, growable via structural mutation.
Definition neat_genome.hpp:94
void MutateWeights(double sigma, double mutation_probability, RNG &rng)
Non-structural mutation: perturbs every enabled connection's weight independently with probability mu...
Definition neat_genome.hpp:271
bool AddConnection(InnovationTracker &tracker, RNG &rng)
RNG-driven wrapper: proposes a random, currently-nonexistent, feedforward-safe (cannot create a cycle...
Definition neat_genome.hpp:181
bool AddNode(InnovationTracker &tracker, RNG &rng)
RNG-driven wrapper: splits a uniformly-randomly chosen enabled connection. A no-op (returns false) if...
Definition neat_genome.hpp:246
Definition acquisition_functions.hpp:16
NEATEvolutionResult RunNEATEvolution(std::vector< NEATGenome > population, FitnessFn fitness_fn, int num_generations, double compatibility_threshold, double c1, double c2, double c3, double weight_mutation_sigma, double weight_mutation_probability, double add_connection_probability, double add_node_probability, InnovationTracker &tracker, RNG &rng)
Runs num_generations of speciated, mutation-only NEAT evolution. Each generation: evaluates every gen...
Definition neat_evolution.hpp:119
NEATGenome ReproduceOffspring(const NEATGenome &parent, InnovationTracker &tracker, RNG &rng, double weight_mutation_sigma, double weight_mutation_probability, double add_connection_probability, double add_node_probability)
RNG-driven wrapper: clones parent, then applies weight mutation (gated per-connection by weight_mutat...
Definition neat_evolution.hpp:85
std::vector< double > ComputeAdjustedFitness(const std::vector< double > &raw_fitness, const std::vector< std::vector< size_t > > &species)
Fitness sharing: each individual's adjusted fitness is its own raw fitness divided by the size of its...
Definition neat_speciation.hpp:134
SpeciesAssignment SpeciatePopulation(const std::vector< NEATGenome > &population, double compatibility_threshold, double c1, double c2, double c3)
Groups population into species: each genome joins the first existing species whose representative (th...
Definition neat_speciation.hpp:96
std::vector< int > AllocateOffspringCounts(const std::vector< double > &species_adjusted_fitness_sums, int population_size)
Pure core: allocates population_size offspring slots across species proportionally to each species' o...
Definition neat_evolution.hpp:37
NEAT genome (Stanley & Miikkulainen, "Evolving Neural Networks through Augmenting Topologies,...
NEAT speciation (Stanley & Miikkulainen 2002): a compatibility-distance metric over two genomes' gene...
Result of a full NEAT evolutionary run.
Definition neat_evolution.hpp:102
NEATGenome best_genome
Definition neat_evolution.hpp:103
double best_fitness
Definition neat_evolution.hpp:104
int generations_run
Definition neat_evolution.hpp:105
Population grouping into species: each inner vector is a list of indices into the population vector t...
Definition neat_speciation.hpp:85
std::vector< std::vector< size_t > > species
Definition neat_speciation.hpp:86