Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
nsga2.cpp
Go to the documentation of this file.
1#include "nsga2.h"
2
3namespace Brush {
4namespace Sel {
5
6using namespace Brush;
7using namespace Pop;
8using namespace Sel;
9
10template<ProgramType T>
12{
13 this->name = "nsga2";
14 this->survival = surv;
15}
16
17template<ProgramType T>
18size_t NSGA2<T>::tournament(Population<T>& pop, size_t i, size_t j) const
19{
20 // gets two individuals and compares them. i and j bhould be within island range
21 const Individual<T>& ind1 = pop[i];
22 const Individual<T>& ind2 = pop[j];
23
24 int flag = ind1.fitness.dominates(ind2.fitness);
25
26 if (flag == 1) // ind1 dominates ind2
27 return i;
28 else if (flag == -1) // ind2 dominates ind1
29 return j;
30 else if (ind1.fitness.crowding_dist > ind2.fitness.crowding_dist)
31 return i;
32 else if (ind2.fitness.crowding_dist > ind1.fitness.crowding_dist)
33 return j;
34 else
35 return i;
36}
37
38template<ProgramType T>
39vector<size_t> NSGA2<T>::select(Population<T>& pop, int island,
40 const Parameters& params)
41{
42 // tournament selection.
43
44 // TODO: move this to tournament selection file, and throw not implemented error in nsga.
45 auto island_pool = pop.get_island_indexes(island);
46
47 // Filter out nullptr individuals (offspring slots)
48 island_pool.erase(
49 std::remove_if(island_pool.begin(), island_pool.end(),
50 [&pop](size_t idx) { return pop.individuals.at(idx) == nullptr; }),
51 island_pool.end()
52 );
53
54 // If all individuals were nullptr, return empty selection
55 if (island_pool.empty())
56 return island_pool;
57
58 // if this is first generation, just return indices to pop
59 if (params.current_gen==0)
60 return island_pool;
61
62 // i am not sure if I need this update of rank and crowding distance (bc first generation is ignored by if above, and the other generations will always have individuals that went through survival, which already calculates this information. TODO: in the final algorithm, I need to make sure this is correct)
63 auto front = fast_nds(pop, island_pool);
64 for (size_t i = 0; i< front.size(); i++)
65 {
66 crowding_distance(pop, front, i);
67 }
68
69 vector<size_t> selected(0);
70 for (int i = 0; i < island_pool.size(); ++i) // selecting based on island_pool size
71 {
72 size_t winner = tournament(pop,
73 *r.select_randomly(island_pool.begin(), island_pool.end()),
74 *r.select_randomly(island_pool.begin(), island_pool.end()));
75
76 selected.push_back(winner);
77 }
78 return selected;
79}
80
81template<ProgramType T>
82vector<size_t> NSGA2<T>::survive(Population<T>& pop, int island,
83 const Parameters& params)
84{
85 // TODO: clean up comments mess here
86
87 // combining all islands to get the pareto fronts
88 std::vector<size_t> island_pool;
89 for (int i = 0; i < params.num_islands; ++i) {
90 auto indexes = pop.get_island_indexes(i);
91 island_pool.insert(island_pool.end(), indexes.begin(), indexes.end());
92 }
93
94 // Filter out nullptr individuals (offspring slots that haven't been filled yet)
95 island_pool.erase(
96 std::remove_if(island_pool.begin(), island_pool.end(),
97 [&pop](size_t idx) { return pop.individuals.at(idx) == nullptr; }),
98 island_pool.end()
99 );
100
101 // If all individuals were nullptr, throw error
102 if (island_pool.empty()) {
103 HANDLE_ERROR_THROW(fmt::format(
104 "NSGA2::survive - island_pool is empty after filtering nullptrs. "
105 "This suggests population was not properly initialized or variation failed."));
106 }
107
108 // fast non-dominated sort
109 auto front = fast_nds(pop, island_pool);
110
111 // Push back selected individuals until full
112 vector<size_t> selected;
113 selected.resize(0);
114
115 int i = 0;
116 while (
117 i < front.size()
118 && ( selected.size() + front.at(i).size() < params.pop_size )
119 )
120 {
121 std::vector<int>& Fi = front.at(i); // indices in front i
122
123 crowding_distance(pop, front, i); // calculate crowding in Fi
124
125 for (int j = 0; j < Fi.size(); ++j) // Pt+1 = Pt+1 U Fi
126 selected.push_back(Fi.at(j));
127
128 ++i;
129 }
130
131 // fmt::print("crowding distance\n");
132 crowding_distance(pop, front, i); // calculate crowding in final front to include
133 std::stable_sort(front.at(i).begin(),front.at(i).end(),sort_n(pop));
134
135 // fmt::print("adding last front)\n");
136 const int extra = params.pop_size - selected.size();
137
138 // Safety check: ensure we don't try to add more than what's available
139 const int available = front.at(i).size();
140 const int to_add = std::min(extra, available);
141
142 for (int j = 0; j < to_add; ++j) // Pt+1 = Pt+1 U Fi[1:N-|Pt+1|]
143 selected.push_back(front.at(i).at(j));
144
145 // If we still don't have enough individuals after all fronts, something is wrong
146 if (selected.size() != params.pop_size) {
147 throw std::runtime_error(fmt::format(
148 "NSGA2 survive: Could not select {} survivors. Only found {} valid individuals in population.",
149 params.pop_size, selected.size()
150 ));
151 }
152
153 // fmt::print("returning\n");
154 return selected;
155}
156
157template<ProgramType T>
158vector<vector<int>> NSGA2<T>::fast_nds(Population<T>& pop, vector<size_t>& island_pool)
159{
160 // this will update pareto dominance attributes in fitness class
161 // based on the population
162
163 //< the Pareto fronts
164 vector<vector<int>> front;
165
166 front.resize(1);
167 front.at(0).clear();
168
169 for (int i = 0; i < island_pool.size(); ++i) {
170
171 std::vector<unsigned int> dom;
172 int dcount = 0;
173
174 auto p = pop.individuals.at(island_pool[i]);
175
176 for (int j = 0; j < island_pool.size(); ++j) {
177
178 const Individual<T>& q = pop[island_pool[j]];
179
180 int compare = p->fitness.dominates(q.fitness);
181 if (compare == 1) { // p dominates q
182 //p.dominated.push_back(j);
183 dom.push_back(island_pool[j]);
184 } else if (compare == -1) { // q dominates p
185 //p.dcounter += 1;
186 dcount += 1;
187 }
188 }
189 p->fitness.dcounter = dcount;
190 p->fitness.dominated = dom; // dom will have values already referring to island indexes
191 // p->fitness.set_crowding_dist(0.0f);
192
193 if (p->fitness.dcounter == 0) {
194 // fmt::print("pushing {}...\n", island_pool[i]);
195 p->fitness.set_rank(1);
196 // front will have values already referring to island indexes
197 front.at(0).push_back(island_pool[i]);
198 }
199
200 }
201
202 // fmt::print("First front size {}...\n", front.at(0).size());
203
204 // using OpenMP can have different orders in the front.at(0)
205 // so let's sort it so that the algorithm is deterministic
206 // given a seed
207 std::stable_sort(front.at(0).begin(), front.at(0).end());
208
209 int fi = 1;
210 while (front.at(fi-1).size() > 0) {
211 std::vector<int>& fronti = front.at(fi-1);
212 std::vector<int> Q;
213 for (int i = 0; i < fronti.size(); ++i) {
214
215 const Individual<T>& p = pop[fronti.at(i)];
216
217 // iterating over dominated individuals
218 for (int j = 0; j < p.fitness.dominated.size(); ++j) {
219 // fmt::print("decreased counter of ind {} for {} to {} \n", j, p.fitness.dominated.at(j), pop.individuals.at(p.fitness.dominated.at(j))->fitness.dcounter);
220
221 auto q = pop.individuals.at(p.fitness.dominated.at(j));
222
223 // fmt::print("decreased counter \n");
224 q->fitness.dcounter -= 1;
225
226 if (q->fitness.dcounter == 0) {
227 // fmt::print("updated counter for ind {} \n", j);
228
229 q->fitness.set_rank(fi+1);
230 Q.push_back(p.fitness.dominated.at(j));
231 }
232 }
233 }
234
235 front.push_back(Q);
236
237 fi += 1;
238 }
239 return front;
240}
241
242template<ProgramType T>
243void NSGA2<T>::crowding_distance(Population<T>& pop, vector<vector<int>>& front, int fronti)
244{
245
246 // fmt::print("inside crowding distance for front {}...\n", fronti);
247
248 std::vector<int> F = front.at(fronti);
249 if (F.size() == 0 ){
250 // fmt::print("empty front\n");
251 return;
252 }
253
254 const int fsize = F.size();
255 // fmt::print("front size is {}...\n", fsize);
256
257 for (int i = 0; i < fsize; ++i)
258 pop.individuals.at(F.at(i))->fitness.set_crowding_dist(0.0f);
259
260 // fmt::print("reseted crowding distance for individuals in this front\n");
261
262 const int limit = pop.individuals.at(0)->fitness.get_wvalues().size();
263 // fmt::print("limit is {}\n", limit);
264
265 for (int m = 0; m < limit; ++m) {
266 // fmt::print("m {}\n", m);
267
268 std::stable_sort(F.begin(), F.end(), comparator_obj(pop,m));
269
270 // in the paper dist=INF for the first and last, in the code
271 // this is only done to the first one or to the two first when size=2
272 pop.individuals.at(F.at(0))->fitness.crowding_dist = std::numeric_limits<float>::max();
273 if (fsize > 1)
274 pop.individuals.at(F.at(fsize-1))->fitness.crowding_dist = std::numeric_limits<float>::max();
275
276 float first_of_front = pop.individuals.at(F.at(0))->fitness.get_wvalues().at(m);
277 float last_of_front = pop.individuals.at(F.at(fsize-1))->fitness.get_wvalues().at(m);
278 for (int i = 1; i < fsize-1; ++i)
279 {
280 if (pop.individuals.at(F.at(i))->fitness.crowding_dist != std::numeric_limits<float>::max())
281 {
282 float next_of_front = pop.individuals.at(F.at(i+1))->fitness.get_wvalues().at(m);
283 float prev_of_front = pop.individuals.at(F.at(i-1))->fitness.get_wvalues().at(m);
284
285 // updating the value by aggregating crowd dist for each objective
286 pop.individuals.at(F.at(i))->fitness.crowding_dist +=
287 (next_of_front - prev_of_front) / (last_of_front - first_of_front);
288 }
289 }
290 }
291}
292
293} // selection
294} // Brush
Fitness fitness
aggregate fitness score
Definition individual.h:37
vector< size_t > get_island_indexes(int island)
Definition population.h:39
vector< std::shared_ptr< Individual< T > > > individuals
Definition population.h:19
void crowding_distance(Population< T > &, vector< vector< int > > &, int)
Definition nsga2.cpp:243
size_t tournament(Population< T > &pop, size_t i, size_t j) const
Definition nsga2.cpp:18
vector< size_t > survive(Population< T > &pop, int island, const Parameters &p)
survival according to the survival scheme of NSGA-II
Definition nsga2.cpp:82
NSGA2(bool surv=false)
Definition nsga2.cpp:11
vector< vector< int > > fast_nds(Population< T > &, vector< size_t > &)
Definition nsga2.cpp:158
vector< size_t > select(Population< T > &pop, int island, const Parameters &p)
selection according to the survival scheme of NSGA-II
Definition nsga2.cpp:39
#define HANDLE_ERROR_THROW(err)
Definition error.h:27
static Rnd & r
Definition rnd.h:176
< nsga2 selection operator for getting the front
Definition bandit.cpp:4
float crowding_dist
crowding distance on the Pareto front
Definition fitness.h:50
vector< unsigned int > dominated
individual indices this dominates
Definition fitness.h:48
int dominates(const Fitness &b) const
set obj vector given a string of objective names
Definition fitness.cpp:55
unsigned int current_gen
Definition params.h:29
sort based on objective m
Definition nsga2.h:65
sort based on rank, breaking ties with crowding distance
Definition nsga2.h:44