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 // if this is first generation, just return indices to pop
48 if (params.current_gen==0)
49 return island_pool;
50
51 // 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)
52 auto front = fast_nds(pop, island_pool);
53 for (size_t i = 0; i< front.size(); i++)
54 {
55 crowding_distance(pop, front, i);
56 }
57
58 vector<size_t> selected(0);
59 for (int i = 0; i < island_pool.size(); ++i) // selecting based on island_pool size
60 {
61 size_t winner = tournament(pop,
62 *r.select_randomly(island_pool.begin(), island_pool.end()),
63 *r.select_randomly(island_pool.begin(), island_pool.end()));
64
65 selected.push_back(winner);
66 }
67 return selected;
68}
69
70template<ProgramType T>
71vector<size_t> NSGA2<T>::survive(Population<T>& pop, int island,
72 const Parameters& params)
73{
74 // TODO: clean up comments mess here
75
76 // combining all islands to get the pareto fronts
77 std::vector<size_t> island_pool;
78 for (int i = 0; i < params.num_islands; ++i) {
79 auto indexes = pop.get_island_indexes(i);
80 island_pool.insert(island_pool.end(), indexes.begin(), indexes.end());
81 }
82
83 // fast non-dominated sort
84 auto front = fast_nds(pop, island_pool);
85
86 // Push back selected individuals until full
87 vector<size_t> selected;
88 selected.resize(0);
89
90 int i = 0;
91 while (
92 i < front.size()
93 && ( selected.size() + front.at(i).size() < params.pop_size )
94 )
95 {
96 std::vector<int>& Fi = front.at(i); // indices in front i
97
98 crowding_distance(pop, front, i); // calculate crowding in Fi
99
100 for (int j = 0; j < Fi.size(); ++j) // Pt+1 = Pt+1 U Fi
101 selected.push_back(Fi.at(j));
102
103 ++i;
104 }
105
106 // fmt::print("crowding distance\n");
107 crowding_distance(pop, front, i); // calculate crowding in final front to include
108 std::sort(front.at(i).begin(),front.at(i).end(),sort_n(pop));
109
110 // fmt::print("adding last front)\n");
111 const int extra = params.pop_size - selected.size();
112 for (int j = 0; j < extra; ++j) // Pt+1 = Pt+1 U Fi[1:N-|Pt+1|]
113 selected.push_back(front.at(i).at(j));
114
115 // fmt::print("returning\n");
116 return selected;
117}
118
119template<ProgramType T>
120vector<vector<int>> NSGA2<T>::fast_nds(Population<T>& pop, vector<size_t>& island_pool)
121{
122 // this will update pareto dominance attributes in fitness class
123 // based on the population
124
125 //< the Pareto fronts
126 vector<vector<int>> front;
127
128 front.resize(1);
129 front.at(0).clear();
130
131 for (int i = 0; i < island_pool.size(); ++i) {
132
133 std::vector<unsigned int> dom;
134 int dcount = 0;
135
136 auto p = pop.individuals.at(island_pool[i]);
137
138 for (int j = 0; j < island_pool.size(); ++j) {
139
140 const Individual<T>& q = pop[island_pool[j]];
141
142 int compare = p->fitness.dominates(q.fitness);
143 if (compare == 1) { // p dominates q
144 //p.dominated.push_back(j);
145 dom.push_back(island_pool[j]);
146 } else if (compare == -1) { // q dominates p
147 //p.dcounter += 1;
148 dcount += 1;
149 }
150 }
151 p->fitness.dcounter = dcount;
152 p->fitness.dominated = dom; // dom will have values already referring to island indexes
153 // p->fitness.set_crowding_dist(0.0f);
154
155 if (p->fitness.dcounter == 0) {
156 // fmt::print("pushing {}...\n", island_pool[i]);
157 p->fitness.set_rank(1);
158 // front will have values already referring to island indexes
159 front.at(0).push_back(island_pool[i]);
160 }
161
162 }
163
164 // fmt::print("First front size {}...\n", front.at(0).size());
165
166 // using OpenMP can have different orders in the front.at(0)
167 // so let's sort it so that the algorithm is deterministic
168 // given a seed
169 std::sort(front.at(0).begin(), front.at(0).end());
170
171 int fi = 1;
172 while (front.at(fi-1).size() > 0) {
173 std::vector<int>& fronti = front.at(fi-1);
174 std::vector<int> Q;
175 for (int i = 0; i < fronti.size(); ++i) {
176
177 const Individual<T>& p = pop[fronti.at(i)];
178
179 // iterating over dominated individuals
180 for (int j = 0; j < p.fitness.dominated.size(); ++j) {
181 // 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);
182
183 auto q = pop.individuals.at(p.fitness.dominated.at(j));
184
185 // fmt::print("decreased counter \n");
186 q->fitness.dcounter -= 1;
187
188 if (q->fitness.dcounter == 0) {
189 // fmt::print("updated counter for ind {} \n", j);
190
191 q->fitness.set_rank(fi+1);
192 Q.push_back(p.fitness.dominated.at(j));
193 }
194 }
195 }
196
197 front.push_back(Q);
198
199 fi += 1;
200 }
201 return front;
202}
203
204template<ProgramType T>
205void NSGA2<T>::crowding_distance(Population<T>& pop, vector<vector<int>>& front, int fronti)
206{
207
208 // fmt::print("inside crowding distance for front {}...\n", fronti);
209
210 std::vector<int> F = front.at(fronti);
211 if (F.size() == 0 ){
212 // fmt::print("empty front\n");
213 return;
214 }
215
216 const int fsize = F.size();
217 // fmt::print("front size is {}...\n", fsize);
218
219 for (int i = 0; i < fsize; ++i)
220 pop.individuals.at(F.at(i))->fitness.set_crowding_dist(0.0f);
221
222 // fmt::print("reseted crowding distance for individuals in this front\n");
223
224 const int limit = pop.individuals.at(0)->fitness.get_wvalues().size();
225 // fmt::print("limit is {}\n", limit);
226
227 for (int m = 0; m < limit; ++m) {
228 // fmt::print("m {}\n", m);
229
230 std::sort(F.begin(), F.end(), comparator_obj(pop,m));
231
232 // in the paper dist=INF for the first and last, in the code
233 // this is only done to the first one or to the two first when size=2
234 pop.individuals.at(F.at(0))->fitness.crowding_dist = std::numeric_limits<float>::max();
235 if (fsize > 1)
236 pop.individuals.at(F.at(fsize-1))->fitness.crowding_dist = std::numeric_limits<float>::max();
237
238 float first_of_front = pop.individuals.at(F.at(0))->fitness.get_wvalues().at(m);
239 float last_of_front = pop.individuals.at(F.at(fsize-1))->fitness.get_wvalues().at(m);
240 for (int i = 1; i < fsize-1; ++i)
241 {
242 if (pop.individuals.at(F.at(i))->fitness.crowding_dist != std::numeric_limits<float>::max())
243 {
244 float next_of_front = pop.individuals.at(F.at(i+1))->fitness.get_wvalues().at(m);
245 float prev_of_front = pop.individuals.at(F.at(i-1))->fitness.get_wvalues().at(m);
246
247 // updating the value by aggregating crowd dist for each objective
248 pop.individuals.at(F.at(i))->fitness.crowding_dist +=
249 (next_of_front - prev_of_front) / (last_of_front - first_of_front);
250 }
251 }
252 }
253}
254
255} // selection
256} // 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:205
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:71
NSGA2(bool surv=false)
Definition nsga2.cpp:11
vector< vector< int > > fast_nds(Population< T > &, vector< size_t > &)
Definition nsga2.cpp:120
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
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