pulsatrix
Loading...
Searching...
No Matches
nsga2.hpp
Go to the documentation of this file.
1
20#pragma once
21
22#include <algorithm>
23#include <limits>
24#include <numeric>
25#include <stdexcept>
26#include <vector>
27
29
30namespace pulsatrix {
31
32using Objectives = std::vector<double>;
33
39inline bool Dominates(const Objectives& a, const Objectives& b) {
40 if (a.size() != b.size()) {
41 throw std::invalid_argument("Dominates: objective vectors must have equal size");
42 }
43 bool strictly_better_in_one = false;
44 for (size_t i = 0; i < a.size(); ++i) {
45 if (a[i] < b[i]) {
46 return false;
47 }
48 if (a[i] > b[i]) {
49 strictly_better_in_one = true;
50 }
51 }
52 return strictly_better_in_one;
53}
54
62inline std::vector<std::vector<size_t>> FastNonDominatedSort(
63 const std::vector<Objectives>& objectives) {
64 const size_t n = objectives.size();
65 std::vector<size_t> domination_count(n, 0);
66 std::vector<std::vector<size_t>> dominates_set(n);
67 std::vector<std::vector<size_t>> fronts;
68 std::vector<size_t> current_front;
69
70 for (size_t p = 0; p < n; ++p) {
71 for (size_t q = 0; q < n; ++q) {
72 if (p == q) {
73 continue;
74 }
75 if (Dominates(objectives[p], objectives[q])) {
76 dominates_set[p].push_back(q);
77 } else if (Dominates(objectives[q], objectives[p])) {
78 domination_count[p]++;
79 }
80 }
81 if (domination_count[p] == 0) {
82 current_front.push_back(p);
83 }
84 }
85
86 while (!current_front.empty()) {
87 fronts.push_back(current_front);
88 std::vector<size_t> next_front;
89 for (size_t p : current_front) {
90 for (size_t q : dominates_set[p]) {
91 if (--domination_count[q] == 0) {
92 next_front.push_back(q);
93 }
94 }
95 }
96 current_front = std::move(next_front);
97 }
98 return fronts;
99}
100
116inline std::vector<double> CrowdingDistance(const std::vector<Objectives>& front_objectives) {
117 const size_t n = front_objectives.size();
118 std::vector<double> distances(n, 0.0);
119 if (n == 0) {
120 return distances;
121 }
122 const size_t num_objectives = front_objectives[0].size();
123 for (const auto& obj : front_objectives) {
124 if (obj.size() != num_objectives) {
125 throw std::invalid_argument(
126 "CrowdingDistance: every objective vector in a front must have equal size");
127 }
128 }
129 if (n <= 2) {
130 std::fill(distances.begin(), distances.end(), std::numeric_limits<double>::infinity());
131 return distances;
132 }
133
134 for (size_t m = 0; m < num_objectives; ++m) {
135 std::vector<size_t> order(n);
136 std::iota(order.begin(), order.end(), size_t{0});
137 std::stable_sort(order.begin(), order.end(), [&](size_t a, size_t b) {
138 return front_objectives[a][m] < front_objectives[b][m];
139 });
140
141 const double min_val = front_objectives[order.front()][m];
142 const double max_val = front_objectives[order.back()][m];
143 const double range = max_val - min_val;
144
145 distances[order.front()] = std::numeric_limits<double>::infinity();
146 distances[order.back()] = std::numeric_limits<double>::infinity();
147
148 if (range <= 0.0) {
149 continue; // objective m is uniform across this front -- no information to add
150 }
151 for (size_t i = 1; i + 1 < n; ++i) {
152 if (distances[order[i]] == std::numeric_limits<double>::infinity()) {
153 continue; // already a boundary individual under a different objective
154 }
155 distances[order[i]] +=
156 (front_objectives[order[i + 1]][m] - front_objectives[order[i - 1]][m]) / range;
157 }
158 }
159 return distances;
160}
161
172template <typename Genotype>
173std::vector<Individual<Genotype, Objectives>> NSGA2Replacement(
174 const std::vector<Individual<Genotype, Objectives>>& population,
175 std::vector<Individual<Genotype, Objectives>> offspring, size_t mu) {
176 if (population.size() + offspring.size() < mu) {
177 throw std::invalid_argument(
178 "NSGA2Replacement: population.size() + offspring.size() must be >= mu");
179 }
180
181 std::vector<Individual<Genotype, Objectives>> combined = population;
182 combined.insert(combined.end(), std::make_move_iterator(offspring.begin()),
183 std::make_move_iterator(offspring.end()));
184
185 std::vector<Objectives> objectives;
186 objectives.reserve(combined.size());
187 for (const auto& ind : combined) {
188 objectives.push_back(ind.fitness);
189 }
190 auto fronts = FastNonDominatedSort(objectives);
191
192 std::vector<Individual<Genotype, Objectives>> next_generation;
193 next_generation.reserve(mu);
194 for (const auto& front : fronts) {
195 if (next_generation.size() + front.size() <= mu) {
196 for (size_t idx : front) {
197 next_generation.push_back(combined[idx]);
198 }
199 if (next_generation.size() == mu) {
200 break;
201 }
202 } else {
203 std::vector<Objectives> front_objectives;
204 front_objectives.reserve(front.size());
205 for (size_t idx : front) {
206 front_objectives.push_back(combined[idx].fitness);
207 }
208 auto distances = CrowdingDistance(front_objectives);
209
210 std::vector<size_t> order(front.size());
211 std::iota(order.begin(), order.end(), size_t{0});
212 std::stable_sort(order.begin(), order.end(),
213 [&](size_t a, size_t b) { return distances[a] > distances[b]; });
214
215 const size_t remaining = mu - next_generation.size();
216 for (size_t i = 0; i < remaining; ++i) {
217 next_generation.push_back(combined[front[order[i]]]);
218 }
219 break;
220 }
221 }
222 return next_generation;
223}
224
225} // namespace pulsatrix
A genetic-algorithm candidate solution: a genotype paired with its fitness.
Definition acquisition_functions.hpp:16
std::vector< Individual< Genotype, Objectives > > NSGA2Replacement(const std::vector< Individual< Genotype, Objectives > > &population, std::vector< Individual< Genotype, Objectives > > offspring, size_t mu)
NSGA-II survivor selection: combines population and offspring, fast-non-dominated- sorts the pool,...
Definition nsga2.hpp:173
bool Dominates(const Objectives &a, const Objectives &b)
Pareto dominance (maximization convention): a dominates b iff a[i] >= b[i] for every objective i,...
Definition nsga2.hpp:39
std::vector< std::vector< size_t > > FastNonDominatedSort(const std::vector< Objectives > &objectives)
Fast non-dominated sort (Deb et al. 2002, Algorithm: fast-non-dominated-sort): partitions [0,...
Definition nsga2.hpp:62
std::vector< double > CrowdingDistance(const std::vector< Objectives > &front_objectives)
Crowding distance within a single front.
Definition nsga2.hpp:116
std::vector< double > Objectives
Definition nsga2.hpp:32
A single candidate solution in a genetic algorithm population.
Definition individual.hpp:25