nsga2.cpp
1 /********************************************************************************
2  * FARSA Genetic Algorithm Library *
3  * Copyright (C) 2007-2011 Gianluca Massera <emmegian@yahoo.it> *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the Free Software *
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18  ********************************************************************************/
19 
20 #include "factory.h"
21 #include "gas/nsga2.h"
22 #include "core/reproduction.h"
23 #include "core/genome.h"
24 #include "core/evaluation.h"
25 #include <cmath>
26 #include <cfloat>
27 #include <QThreadPool>
28 #include <QtConcurrentMap>
29 using namespace QtConcurrent;
30 
31 namespace farsa {
32 
33 NSGA2::NSGA2()
34  : GeneticAlgo(), lastPareto() {
35  fitfunc = 0;
36  reprod = 0;
37  numGens = 0;
38  currPhase = initEvaluation;
39  numThreadv = 1;
40  isInitialized = false;
41  isFinalized = true;
42 }
43 
45  foreach( NSGA2::evaluationThread* e, evalThreads ) {
46  delete e;
47  }
48  delete fitfunc;
49 }
50 
51 void NSGA2::configure( ConfigurationParameters& params, QString prefix ) {
52  setGenome( params.getObjectFromGroup<Genome>( prefix + QString( "GENOME" ) ) );
53  setEvaluation( params.getObjectFromGroup<Evaluation>( prefix + QString( "EVALUATION" ) ) );
54  fitfunc->setGenome( genome() );
55  setReproduction( params.getObjectFromGroup<Reproduction>( prefix + QString( "REPRODUCTION" ) ) );
56  setNumGenerations( params.getValue( prefix + QString( "ngenerations" ) ).toInt() );
57  if ( numGenerations() == 0 ) {
58  qWarning( "Setting number of Generations to ZERO!! Check your config file" );
59  }
60  setNumThreads( params.getValue( prefix + QString( "numThreads" ) ).toInt() );
61 }
62 
63 void NSGA2::save( ConfigurationParameters& params, QString prefix ) {
64  params.createParameter( prefix, QString("type"), "NSGA2" );
65  params.createParameter( prefix, QString("numThreads"), QString("%1").arg( numThreads() ) );
66  params.createParameter( prefix, QString("ngenerations"), QString("%1").arg( numGenerations() ) );
67  //--- EVALUATION
68  fitfunc->save( params, params.createSubGroup( prefix, "EVALUATION" ) );
69  //--- REPRODUCTION
70  reproduction()->save( params, params.createSubGroup( prefix, "REPRODUCTION" ) );
71  //--- GENOME
72  genome()->save( params, params.createSubGroup( prefix, "GENOME" ) );
73 }
74 
75 void NSGA2::describe( QString type ) {
76  Descriptor d = addTypeDescription( type, "Non-dominated Sorting Genetic Algorithm version 2" );
77  d.describeInt( "numThreads" ).limits( 1, 32 ).help( "Number of threads to parallelize the evaluation of individuals" );
78  d.describeInt( "ngenerations" ).limits( 1, INT_MAX ).def( 1000 ).help( "Number of the generations of the evolutionary process" );
79  d.describeSubgroup( "EVALUATION" ).type( "Evaluation" ).props( IsMandatory ).help( "Object that calculate the fitness", "Create a subclass of Evalution and code your custom fitness function" );
80  d.describeSubgroup( "REPRODUCTION" ).type( "Reproduction" ).props( IsMandatory ).help( "Object that generate the new generations" );
81  d.describeSubgroup( "GENOME" ).type( "Genome" ).props( IsMandatory ).help( "Object containing the individuals under evolution" );
82 }
83 
84 void NSGA2::setNumThreads( int numThreads ) {
85  if ( numThreads < 1 ) {
86  qWarning( "The number of Threads must be greater than one!!" );
87  }
88  Q_ASSERT_X( !isInitialized && isFinalized ,
89  "NSGA2::setNumThreads",
90  "This method can only called before initialize of NSGA2" );
91  Q_ASSERT_X( fitfunc != 0 ,
92  "NSGA2::setNumThreads",
93  "This method must be called after an Evaluation object has been setted by NSGA2::setEvaluation" );
94  numThreadv = numThreads;
95  return;
96 }
97 
99  return numThreadv;
100 }
101 
103  this->fitfunc = fitfunc;
104  this->fitfunc->setGA( this );
105 }
106 
108 {
109  return fitfunc;
110 }
111 
112 QVector<Evaluation*> NSGA2::evaluationPool() {
113  QVector<Evaluation*> ev;
114  foreach( NSGA2::evaluationThread* e, evalThreads ) {
115  ev.append( e->eval );
116  }
117  return ev;
118 }
119 
121  this->reprod = reprod;
122  this->reprod->setGA( this );
123 }
124 
126  return reprod;
127 }
128 
130  if ( isInitialized && !isFinalized ) return;
131  Q_ASSERT_X( fitfunc != 0 ,
132  "NSGA2::initialize",
133  "You have to setup the Evaluation object of NSGA2 (Fitness Function)" );
134  Q_ASSERT_X( reprod !=0 ,
135  "NSGA2::initialize",
136  "You have to setup the Reproduction operator of NSGA2" );
137 
138  isInitialized = true;
139  isFinalized = false;
140  setGeneration( 0 );
141  currPhase = initEvaluation;
142  evolutionEnd = false;
143  evaluationDone = false;
144  //--- Setting up the evalThreads
145  for( int i=0; i<evalThreads.size(); i++ ) {
146  delete (evalThreads[i]);
147  }
148  evalThreads.clear();
149  for( int i=0; i<numThreadv; i++ ) {
150  evalThreads.append( new evaluationThread( this, fitfunc ) );
151  }
152  //--- set the number of thread to create
153  QThreadPool::globalInstance()->setMaxThreadCount( numThreadv );
154 }
155 
157  switch( currPhase ) {
158  case initEvaluation:
159  for( int i=0; i<numThreadv; i++ ) {
160  evalThreads[i]->sequence.clear();
161  }
162  for( int i=0; i<(int)genome()->size(); i++ ) {
163  evalThreads[ i%numThreadv ]->sequence.append( i );
164  }
165  for( int i=0; i<numThreadv; i++ ) {
166  evalThreads[i]->idSeq = 0;
167  evalThreads[i]->id = evalThreads[i]->sequence[ evalThreads[i]->idSeq ];
168  evalThreads[i]->eval->setGenome( genome() );
169  evalThreads[i]->eval->initialize( genome()->at( evalThreads[i]->id ) );
170  evalThreads[i]->blocked = false;
171  }
172  currPhase = evaluating;
173  break;
174  case evaluating: { /* Multi Thread Block (i.e. Parallel Evaluation of Genotypes */
175  nextGeneration = true;
176  if ( numThreadv == 1 ) {
177  // Don't use Threads if is not necessary
178  evalThreads[0]->runStep();
179  } else {
180  QFuture<void> future = map( evalThreads, NSGA2::runStepWrapper );
181  future.waitForFinished();
182  }
183  if ( nextGeneration ) {
184  currPhase = nextGeneration_pass1;
185  }
186  } /* End of Multi Thread Block */
187  break;
188  case nextGeneration_pass1: {
189  // --- Core of the NSGA-II Algorithm
190  // -1) merge previous front population lastPareto with current genome()
191  //--- this merge assure that the best (elite) pareto-fronts are mantained
192  nsgaGenome allGenome;
193  for( unsigned int i=0; i<lastPareto.size(); i++ ) {
194  allGenome.append( new nsgaGenotype( lastPareto.at(i), 0, 0.0 ) );
195  }
196  for( unsigned int i=0; i<genome()->size(); i++ ) {
197  allGenome.append( new nsgaGenotype( genome()->at(i), 0, 0.0 ) );
198  }
199  // -2) fastNonDominatedSort( onMergedPopulation )
200  //--- genotype grouped by the front rank
201  QVector<nsgaGenome> frontsByRank = fastNonDominatedSort( allGenome );
202  // -3) create new population of size genome().size() using the fronts
203  // calculated and use the crowdingDistanceAssignment
204  unsigned int currentGenotype = 0;
205  int numOfFronts = frontsByRank.size();
206  int numObjs = allGenome[0]->genotype->numOfObjectives();
207  for( int front=0; front<numOfFronts; front++ ) {
208  //--- sort it on crowding distance and add to the new Genome
209  crowdingDistanceAssignment( frontsByRank[front] );
210  qStableSort( frontsByRank[front].begin(), frontsByRank[front].end(), NSGA2::crowdingDistanceGreaterThan );
211  foreach( nsgaGenotype* gen, frontsByRank[front] ) {
212  //--- assign the rank
213  gen->genotype->setRank( 2*(numOfFronts - front) + (gen->distance/numObjs) );
214  genome()->set( currentGenotype, gen->genotype );
215  currentGenotype++;
216  if ( currentGenotype == genome()->size() ) break;
217  }
218  if ( currentGenotype == genome()->size() ) break;
219  }
220  // -4) lastPareto <- genome()
221  //--- this pass correspond to elite the pareto-fronts
222  lastPareto = *(genome());
223  // -5) clean up memory
224  for( int i=0; i<allGenome.size(); i++ ) {
225  delete (allGenome[i]);
226  }
227  updateStats();
228  evaluationDone = true;
229  if ( generation() < numGens ) {
230  currPhase = nextGeneration_pass2;
231  } else {
232  currPhase = endEvolution;
233  }
234  }
235  break;
236  case nextGeneration_pass2: {
237  //--- this additional pass is for avoid to modify the
238  //--- genotypes contained in the last generation of the evolution
239  //--- and to allow to save the genotypes after each generation
240  Genome* old = genome();
241  setGenome( reprod->reproduction( old ) );
242  delete old;
243  fitfunc->setGenome( genome() );
244  evaluationDone = false;
245  setGeneration( generation()+1 );
246  currPhase = initEvaluation;
247  }
248  break;
249  case endEvolution:
250  finalize();
251  break;
252  default:
253  qFatal( "Default switch in NSGA2::gaStep" );
254  break;
255  }
256 }
257 
259  // Set evaluation done, and check which phase to go to
260  evaluationDone = true;
261  if ( generation() < numGens ) {
262  currPhase = nextGeneration_pass2;
263  } else {
264  currPhase = endEvolution;
265  }
266 }
267 
269  if ( isFinalized && !isInitialized ) return;
270 
271  isInitialized = false;
272  isFinalized = true;
273  evolutionEnd = true;
274 }
275 
276 QVector<NSGA2::nsgaGenome> NSGA2::fastNonDominatedSort( nsgaGenome& pareto ) {
277  QMap<nsgaGenotype*, nsgaGenome> dominatedBy;
278  QVector<nsgaGenome> frontsByRank;
279  frontsByRank.resize(1);
280  //--- create the first front containing the top solutions
281  for( int p=0; p<pareto.size(); p++ ) {
282  //--- domination counter of genP reset to zero
283  pareto[p]->dominationCounter = 0;
284  for( int q=0; q<pareto.size(); q++ ) {
285  if ( p==q ) continue;
286  if ( pareto[q]->genotype->dominatedBy( pareto[p]->genotype ) ) {
287  // OPTIMIZE: it seems that most of the time is spent on accessing the QMap
288  dominatedBy[ pareto[p] ].append( pareto[q] );
289  } else if ( pareto[p]->genotype->dominatedBy( pareto[q]->genotype ) ) {
290  pareto[p]->dominationCounter++;
291  }
292  }
293  //--- if nP == 0 means that genP is a solution belongs to the top pareto-front
294  if ( pareto[p]->dominationCounter == 0 ) {
295  pareto[p]->rank = 0;
296  frontsByRank[0].append( pareto[p] );
297  }
298  }
299  //--- create all the other fronts
300  bool done = false;
301  int currentFront = 0;
302  while( !done ) {
303  nsgaGenome newFront;
304  for( int i=0; i<frontsByRank[currentFront].size(); i++ ) {
305  nsgaGenotype* p = frontsByRank[currentFront][i];
306  nsgaGenome pDominate = dominatedBy[p];
307  for( int q=0; q<pDominate.size(); q++ ) {
308  pDominate[q]->dominationCounter--;
309  if ( pDominate[q]->dominationCounter == 0 ) {
310  pDominate[q]->rank = currentFront+1;
311  newFront.append( pDominate[q] );
312  }
313  }
314  }
315  currentFront++;
316  if ( newFront.isEmpty() ) {
317  done = true;
318  } else {
319  frontsByRank.append( newFront );
320  }
321  }
322  int total = 0;
323  for( int i=0; i<frontsByRank.size(); i++ ) {
324  total += frontsByRank[i].size();
325  }
326  return frontsByRank;
327 }
328 
329 void NSGA2::crowdingDistanceAssignment( nsgaGenome& genome ) {
330  int dimGenome = genome.size();
331  if ( dimGenome == 0 ) return;
332  int numObjs = genome[0]->genotype->numOfObjectives();
333  //--- vectors containing the max and min values of objectives in this genome
334  QVector<double> fmax;
335  QVector<double> fmin;
336  fmax.resize( numObjs );
337  fmin.resize( numObjs );
338  for( int i=0; i<numObjs; i++ ) {
339  fmax[i] = genome[0]->genotype->objective( i );
340  fmin[i] = genome[0]->genotype->objective( i );
341  }
342  //--- initialize distance and calculate fmax and fmin values
343  for( int i=0; i<dimGenome; i++ ) {
344  genome[i]->distance = 0;
345  for( int m=0; m<numObjs; m++ ) {
346  fmax[m] = qMax( fmax[m], genome[i]->genotype->objective(m) );
347  fmin[m] = qMin( fmin[m], genome[i]->genotype->objective(m) );
348  }
349  }
350  //--- calculate the distance
351  nObjectiveGreaterThan objCompare;
352  for( int m=0; m<numObjs; m++ ) {
353  // currentObjective is used by nObjectiveGreaterThan for sorting
354  objCompare.currentObjective = m;
355  qStableSort( genome.begin(), genome.end(), objCompare );
356  // the maximum value is numObj, setting to numObj assure that this two
357  // genotypes are always the top in the current front
358  genome[0]->distance = numObjs; //DBL_MAX;
359  genome.last()->distance = numObjs; //DBL_MAX;
360  for( int i=1; i<dimGenome-1; i++ ) {
361  double m1 = genome[i+1]->genotype->objective(m);
362  double m2 = genome[i-1]->genotype->objective(m);
363  genome[i]->distance += fabs(m1-m2)/(fmax[m]-fmin[m]);
364  // if the value is nan, then it will setted to zero (worst distance)
365  if ( genome[i]->distance != genome[i]->distance ) {
366  genome[i]->distance = 0.0;
367  }
368  }
369  }
370 }
371 
372 NSGA2::evaluationThread::evaluationThread( NSGA2* p, Evaluation* eProto )
373  : parent(p), id(0), blocked(false) {
374  eval = eProto->clone();
375  eval->setGenome( p->genome() );
376  eval->setGA( p );
377 }
378 
379 NSGA2::evaluationThread::~evaluationThread() {
380  delete eval;
381  sequence.clear();
382 }
383 
384 void NSGA2::evaluationThread::runStep() {
385  if ( blocked ) {
386  return;
387  }
388 
389  parent->nextGeneration = false;
390  eval->evaluateStep();
391  if ( eval->isEvaluationDone() ) {
392  int nextIdSeq = idSeq + 1;
393  eval->finalize();
394  if ( nextIdSeq >= sequence.size() ) {
395  blocked = true;
396  return;
397  }
398  idSeq = nextIdSeq;
399  int nextId = sequence[ idSeq ];
400  id = nextId;
401  eval->initialize( parent->genome()->at( id ) );
402  }
403 }
404 
405 void NSGA2::runStepWrapper( NSGA2::evaluationThread* e ) {
406  e->runStep();
407 }
408 
409 } // end namespace farsa