EvolvingObjects
|
00001 00025 //----------------------------------------------------------------------------- 00026 00027 #ifndef eoNDSorting_h 00028 #define eoNDSorting_h 00029 00030 #include <EO.h> 00031 #include <algorithm> 00032 #include <eoPop.h> 00033 #include <eoPerf2Worth.h> 00034 #include <cassert> 00035 00041 template <class EOT> 00042 class eoNDSorting : public eoPerf2WorthCached<EOT, double> 00043 { 00044 public : 00045 00046 using eoPerf2WorthCached<EOT, double>::value; 00047 eoNDSorting(bool nasty_flag_ = false) 00048 : nasty_declone_flag_that_only_is_implemented_for_two_objectives(nasty_flag_) 00049 {} 00050 00051 00052 eoNDSorting() 00053 : nasty_declone_flag_that_only_is_implemented_for_two_objectives(false) 00054 {} 00055 00060 virtual std::vector<double> niche_penalty(const std::vector<unsigned>& current_front, const eoPop<EOT>& _pop) = 0; 00061 00062 void calculate_worths(const eoPop<EOT>& _pop) 00063 { 00064 // resize the worths beforehand 00065 value().resize(_pop.size()); 00066 00067 typedef typename EOT::Fitness::fitness_traits traits; 00068 00069 switch (traits::nObjectives()) 00070 { 00071 case 1: 00072 { 00073 one_objective(_pop); 00074 return; 00075 } 00076 case 2: 00077 { 00078 two_objectives(_pop); 00079 return; 00080 } 00081 default : 00082 { 00083 m_objectives(_pop); 00084 } 00085 } 00086 } 00087 00088 private : 00089 00094 class DummyEO : public EO<typename EOT::Fitness> 00095 { 00096 public: unsigned index; 00097 }; 00098 00099 void one_objective(const eoPop<EOT>& _pop) 00100 { 00101 unsigned i; 00102 std::vector<DummyEO> tmp_pop; 00103 tmp_pop.resize(_pop.size()); 00104 00105 // copy pop to dummy population (only need the fitnesses) 00106 for (i = 0; i < _pop.size(); ++i) 00107 { 00108 tmp_pop[i].fitness(_pop[i].fitness()); 00109 tmp_pop[i].index = i; 00110 } 00111 00112 std::sort(tmp_pop.begin(), tmp_pop.end(), std::greater<DummyEO>()); 00113 00114 for (i = 0; i < _pop.size(); ++i) 00115 { 00116 value()[tmp_pop[i].index] = _pop.size() - i; // set rank 00117 } 00118 00119 // no point in calculcating niche penalty, as every distinct fitness value has a distinct rank 00120 } 00121 00140 void two_objectives(const eoPop<EOT>& _pop) 00141 { 00142 unsigned i; 00143 typedef typename EOT::Fitness::fitness_traits traits; 00144 assert(traits::nObjectives() == 2); 00145 00146 std::vector<unsigned> sort1(_pop.size()); // index into population sorted on first objective 00147 00148 for (i = 0; i < _pop.size(); ++i) 00149 { 00150 sort1[i] = i; 00151 } 00152 00153 std::sort(sort1.begin(), sort1.end(), Sorter(_pop)); 00154 00155 // Ok, now the meat of the algorithm 00156 00157 unsigned last_front = 0; 00158 00159 double max1 = -1e+20; 00160 for (i = 0; i < _pop.size(); ++i) 00161 { 00162 max1 = std::max(max1, _pop[i].fitness()[1]); 00163 } 00164 00165 max1 = max1 + 1.0; // add a bit to it so that it is a real upperbound 00166 00167 unsigned prev_front = 0; 00168 std::vector<double> d; 00169 d.resize(_pop.size(), max1); // initialize with the value max1 everywhere 00170 00171 std::vector<std::vector<unsigned> > fronts(_pop.size()); // to store indices into the front 00172 00173 for (i = 0; i < _pop.size(); ++i) 00174 { 00175 unsigned index = sort1[i]; 00176 00177 // check for clones and delete them 00178 if (i > 0) 00179 { 00180 unsigned prev = sort1[i-1]; 00181 if ( _pop[index].fitness() == _pop[prev].fitness()) 00182 { // it's a clone, give it the worst rank! 00183 00184 if (nasty_declone_flag_that_only_is_implemented_for_two_objectives) 00185 //declone 00186 fronts.back().push_back(index); 00187 else // assign it the rank of the previous 00188 fronts[prev_front].push_back(index); 00189 continue; 00190 } 00191 } 00192 00193 double value2 = _pop[index].fitness()[1]; 00194 00195 if (traits::maximizing(1)) 00196 value2 = max1 - value2; 00197 00198 // perform binary search using std::upper_bound, a log n operation for each member 00199 std::vector<double>::iterator it = 00200 std::upper_bound(d.begin(), d.begin() + last_front, value2); 00201 00202 unsigned front = unsigned(it - d.begin()); 00203 if (front == last_front) ++last_front; 00204 00205 assert(it != d.end()); 00206 00207 *it = value2; //update d 00208 fronts[front].push_back(index); // add it to the front 00209 00210 prev_front = front; 00211 } 00212 00213 // ok, and finally the niche penalty 00214 00215 for (i = 0; i < fronts.size(); ++i) 00216 { 00217 if (fronts[i].size() == 0) continue; 00218 00219 // Now we have the indices to the current front in current_front, do the niching 00220 std::vector<double> niche_count = niche_penalty(fronts[i], _pop); 00221 00222 // Check whether the derived class was nice 00223 if (niche_count.size() != fronts[i].size()) 00224 { 00225 throw std::logic_error("eoNDSorting: niche and front should have the same size"); 00226 } 00227 00228 double max_niche = *std::max_element(niche_count.begin(), niche_count.end()); 00229 00230 for (unsigned j = 0; j < fronts[i].size(); ++j) 00231 { 00232 value()[fronts[i][j]] = i + niche_count[j] / (max_niche + 1.); // divide by max_niche + 1 to ensure that this front does not overlap with the next 00233 } 00234 00235 } 00236 00237 // invert ranks to obtain a 'bigger is better' score 00238 rank_to_worth(); 00239 } 00240 00241 class Sorter 00242 { 00243 public: 00244 Sorter(const eoPop<EOT>& _pop) : pop(_pop) {} 00245 00246 bool operator()(unsigned i, unsigned j) const 00247 { 00248 typedef typename EOT::Fitness::fitness_traits traits; 00249 00250 double diff = pop[i].fitness()[0] - pop[j].fitness()[0]; 00251 00252 if (fabs(diff) < traits::tol()) 00253 { 00254 diff = pop[i].fitness()[1] - pop[j].fitness()[1]; 00255 00256 if (fabs(diff) < traits::tol()) 00257 return false; 00258 00259 if (traits::maximizing(1)) 00260 return diff > 0.; 00261 return diff < 0.; 00262 } 00263 00264 if (traits::maximizing(0)) 00265 return diff > 0.; 00266 return diff < 0.; 00267 } 00268 00269 const eoPop<EOT>& pop; 00270 }; 00271 00272 void m_objectives(const eoPop<EOT>& _pop) 00273 { 00274 unsigned i; 00275 00276 typedef typename EOT::Fitness::fitness_traits traits; 00277 00278 std::vector<std::vector<unsigned> > S(_pop.size()); // which individuals does guy i dominate 00279 std::vector<unsigned> n(_pop.size(), 0); // how many individuals dominate guy i 00280 00281 unsigned j; 00282 for (i = 0; i < _pop.size(); ++i) 00283 { 00284 for (j = 0; j < _pop.size(); ++j) 00285 { 00286 if (_pop[i].fitness().dominates(_pop[j].fitness())) 00287 { // i dominates j 00288 S[i].push_back(j); // add j to i's domination std::list 00289 00290 //n[j]++; // as i dominates j 00291 } 00292 else if (_pop[j].fitness().dominates(_pop[i].fitness())) 00293 { // j dominates i, increment count for i, add i to the domination std::list of j 00294 n[i]++; 00295 00296 //S[j].push_back(i); 00297 } 00298 } 00299 } 00300 00301 std::vector<unsigned> current_front; 00302 current_front.reserve(_pop.size()); 00303 00304 // get the first front out 00305 for (i = 0; i < _pop.size(); ++i) 00306 { 00307 if (n[i] == 0) 00308 { 00309 current_front.push_back(i); 00310 } 00311 } 00312 00313 std::vector<unsigned> next_front; 00314 next_front.reserve(_pop.size()); 00315 00316 unsigned front_index = 0; // which front are we processing 00317 while (!current_front.empty()) 00318 { 00319 // Now we have the indices to the current front in current_front, do the niching 00320 std::vector<double> niche_count = niche_penalty(current_front, _pop); 00321 00322 // Check whether the derived class was nice 00323 if (niche_count.size() != current_front.size()) 00324 { 00325 throw std::logic_error("eoNDSorting: niche and front should have the same size"); 00326 } 00327 00328 double max_niche = *std::max_element(niche_count.begin(), niche_count.end()); 00329 00330 for (i = 0; i < current_front.size(); ++i) 00331 { 00332 value()[current_front[i]] = front_index + niche_count[i] / (max_niche + 1.); // divide by max_niche + 1 to ensure that this front does not overlap with the next 00333 } 00334 00335 // Calculate which individuals are in the next front; 00336 00337 for (i = 0; i < current_front.size(); ++i) 00338 { 00339 for (j = 0; j < S[current_front[i]].size(); ++j) 00340 { 00341 unsigned dominated_individual = S[current_front[i]][j]; 00342 n[dominated_individual]--; // As we remove individual i -- being part of the current front -- it no longer dominates j 00343 00344 if (n[dominated_individual] == 0) // it should be in the current front 00345 { 00346 next_front.push_back(dominated_individual); 00347 } 00348 } 00349 } 00350 00351 front_index++; // go to the next front 00352 swap(current_front, next_front); // make the next front current 00353 next_front.clear(); // clear it for the next iteration 00354 } 00355 00356 rank_to_worth(); 00357 } 00358 00359 void rank_to_worth() 00360 { 00361 // now all that's left to do is to transform lower rank into higher worth 00362 double max_fitness = *std::max_element(value().begin(), value().end()); 00363 00364 // but make sure it's an integer upper bound, so that all ranks inside the highest integer are the front 00365 max_fitness = ceil(max_fitness); 00366 00367 for (unsigned i = 0; i < value().size(); ++i) 00368 { 00369 value()[i] = max_fitness - value()[i]; 00370 } 00371 00372 } 00373 public : bool nasty_declone_flag_that_only_is_implemented_for_two_objectives; 00374 }; 00375 00379 template <class EOT> 00380 class eoNDSorting_I : public eoNDSorting<EOT> 00381 { 00382 public : 00383 eoNDSorting_I(double _nicheSize, bool nasty_flag_ = false) : eoNDSorting<EOT>(nasty_flag_), nicheSize(_nicheSize) {} 00384 00385 std::vector<double> niche_penalty(const std::vector<unsigned>& current_front, const eoPop<EOT>& _pop) 00386 { 00387 std::vector<double> niche_count(current_front.size(), 0.); 00388 00389 for (unsigned i = 0; i < current_front.size(); ++i) 00390 { // calculate whether the other points lie within the nice 00391 for (unsigned j = 0; j < current_front.size(); ++j) 00392 { 00393 if (i == j) 00394 continue; 00395 00396 double dist = 0.0; 00397 00398 for (unsigned k = 0; k < _pop[current_front[j]].fitness().size(); ++k) 00399 { 00400 double d = _pop[current_front[i]].fitness()[k] - _pop[current_front[j]].fitness()[k]; 00401 dist += d*d; 00402 } 00403 00404 if (dist < nicheSize) 00405 { 00406 niche_count[i] += 1.0 - pow(dist / nicheSize,2.); 00407 } 00408 } 00409 } 00410 00411 return niche_count; 00412 } 00413 00414 private : 00415 00416 double nicheSize; 00417 }; 00418 00433 template <class EOT> 00434 class eoNDSorting_II : public eoNDSorting<EOT> 00435 { 00436 public: 00437 00438 eoNDSorting_II(bool nasty_flag_ = false) : eoNDSorting<EOT>(nasty_flag_) {} 00439 00440 typedef std::pair<double, unsigned> double_index_pair; 00441 00442 class compare_nodes 00443 { 00444 public : 00445 bool operator()(const double_index_pair& a, const double_index_pair& b) const 00446 { 00447 return a.first < b.first; 00448 } 00449 }; 00450 00452 std::vector<double> niche_penalty(const std::vector<unsigned>& _cf, const eoPop<EOT>& _pop) 00453 { 00454 typedef typename EOT::Fitness::fitness_traits traits; 00455 unsigned i; 00456 std::vector<double> niche_count(_cf.size(), 0.); 00457 00458 00459 unsigned nObjectives = traits::nObjectives(); //_pop[_cf[0]].fitness().size(); 00460 00461 for (unsigned o = 0; o < nObjectives; ++o) 00462 { 00463 00464 std::vector<std::pair<double, unsigned> > performance(_cf.size()); 00465 for (i =0; i < _cf.size(); ++i) 00466 { 00467 performance[i].first = _pop[_cf[i]].fitness()[o]; 00468 performance[i].second = i; 00469 } 00470 00471 std::sort(performance.begin(), performance.end(), compare_nodes()); // a lambda operator would've been nice here 00472 00473 std::vector<double> nc(niche_count.size(), 0.0); 00474 00475 for (i = 1; i < _cf.size()-1; ++i) 00476 { // calculate distance 00477 nc[performance[i].second] = performance[i+1].first - performance[i-1].first; 00478 } 00479 00480 double max_dist = *std::max_element(nc.begin(), nc.end()); 00481 00482 // set boundary at max_dist + 1 (so it will get chosen over all the others 00483 nc[performance[0].second] += max_dist + 1; 00484 nc[performance.back().second] += max_dist + 1; 00485 00486 for (i = 0; i < nc.size(); ++i) 00487 { 00488 niche_count[i] += (max_dist + 1 - nc[i]); 00489 } 00490 } 00491 00492 return niche_count; 00493 } 00494 }; 00495 00496 #endif