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 "configurationhelper.h"
26 #include <cmath>
27 #include <cfloat>
28 #include <QThreadPool>
29 #include <QtConcurrentMap>
30 using namespace QtConcurrent;
31 
32 namespace farsa {
33 
34 NSGA2::NSGA2()
35  : GeneticAlgo(), lastPareto() {
36  fitfunc = 0;
37  reprod = 0;
38  numGens = 0;
39  currPhase = initEvaluation;
40  numThreadv = 1;
41  isInitialized = false;
42  isFinalized = true;
43 }
44 
46  foreach( NSGA2::evaluationThread* e, evalThreads ) {
47  delete e;
48  }
49  delete fitfunc;
50 }
51 
52 void NSGA2::configure( ConfigurationParameters& params, QString prefix ) {
53  setGenome( params.getObjectFromGroup<Genome>( prefix + QString( "GENOME" ) ) );
54  setEvaluation( params.getObjectFromGroup<Evaluation>( prefix + QString( "EVALUATION" ) ) );
55  fitfunc->setGenome( genome() );
56  setReproduction( params.getObjectFromGroup<Reproduction>( prefix + QString( "REPRODUCTION" ) ) );
57  setNumGenerations( ConfigurationHelper::getInt( params, prefix + QString( "ngenerations" ), 1000 ) );
58  setNumThreads( ConfigurationHelper::getInt( params, prefix + QString( "numThreads" ), 1 ) );
59 }
60 
61 void NSGA2::save( ConfigurationParameters& params, QString prefix ) {
62  params.createParameter( prefix, QString("type"), "NSGA2" );
63  params.createParameter( prefix, QString("numThreads"), QString("%1").arg( numThreads() ) );
64  params.createParameter( prefix, QString("ngenerations"), QString("%1").arg( numGenerations() ) );
65  //--- EVALUATION
66  fitfunc->save( params, params.createSubGroup( prefix, "EVALUATION" ) );
67  //--- REPRODUCTION
68  reproduction()->save( params, params.createSubGroup( prefix, "REPRODUCTION" ) );
69  //--- GENOME
70  genome()->save( params, params.createSubGroup( prefix, "GENOME" ) );
71 }
72 
73 void NSGA2::describe( QString type ) {
74  Descriptor d = addTypeDescription( type, "Non-dominated Sorting Genetic Algorithm version 2" );
75  d.describeInt( "numThreads" ).limits( 1, 32 ).def(1).help( "Number of threads to parallelize the evaluation of individuals" ).runtime(&NSGA2::setNumThreads, &NSGA2::numThreads);;
76  d.describeInt( "ngenerations" ).limits( 1, INT_MAX ).def( 1000 ).help( "Number of the generations of the evolutionary process" );
77  d.describeSubgroup( "EVALUATION" ).type( "Evaluation" ).props( IsMandatory ).help( "Object that calculate the fitness", "Create a subclass of Evalution and code your custom fitness function" );
78  d.describeSubgroup( "REPRODUCTION" ).type( "Reproduction" ).props( IsMandatory ).help( "Object that generate the new generations" );
79  d.describeSubgroup( "GENOME" ).type( "Genome" ).props( IsMandatory ).help( "Object containing the individuals under evolution" );
80 }
81 
82 void NSGA2::setNumThreads( int numThreads ) {
83  if ( numThreads < 1 ) {
84  qWarning( "The number of Threads must be greater than one!!" );
85  }
86  Q_ASSERT_X( !isInitialized && isFinalized ,
87  "NSGA2::setNumThreads",
88  "This method can only called before initialize of NSGA2" );
89  Q_ASSERT_X( fitfunc != 0 ,
90  "NSGA2::setNumThreads",
91  "This method must be called after an Evaluation object has been setted by NSGA2::setEvaluation" );
92  numThreadv = numThreads;
93  return;
94 }
95 
96 int NSGA2::numThreads() const {
97  return numThreadv;
98 }
99 
101  this->fitfunc = fitfunc;
102  this->fitfunc->setGA( this );
103 }
104 
106 {
107  return fitfunc;
108 }
109 
110 QVector<Evaluation*> NSGA2::evaluationPool() {
111  QVector<Evaluation*> ev;
112  foreach( NSGA2::evaluationThread* e, evalThreads ) {
113  ev.append( e->eval );
114  }
115  return ev;
116 }
117 
119  this->reprod = reprod;
120  this->reprod->setGA( this );
121 }
122 
124  return reprod;
125 }
126 
128  if ( isInitialized && !isFinalized ) return;
129  Q_ASSERT_X( fitfunc != 0 ,
130  "NSGA2::initialize",
131  "You have to setup the Evaluation object of NSGA2 (Fitness Function)" );
132  Q_ASSERT_X( reprod !=0 ,
133  "NSGA2::initialize",
134  "You have to setup the Reproduction operator of NSGA2" );
135 
136  isInitialized = true;
137  isFinalized = false;
138  setGeneration( 0 );
139  currPhase = initEvaluation;
140  evolutionEnd = false;
141  evaluationDone = false;
142  getIODelegate()->recoverData(this);
143  //--- Setting up the evalThreads
144  for( int i=0; i<evalThreads.size(); i++ ) {
145  delete (evalThreads[i]);
146  }
147  evalThreads.clear();
148  for( int i=0; i<numThreadv; i++ ) {
149  evalThreads.append( new evaluationThread( this, fitfunc ) );
150  evalThreads.last()->eval->initGeneration( generation() );
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  for( int i=0; i<numThreadv; i++ ) {
230  evalThreads[i]->eval->endGeneration( generation() );
231  }
233  getIODelegate()->saveData(this);
234  if ( generation() < numGens ) {
235  currPhase = nextGeneration_pass2;
236  } else {
237  currPhase = endEvolution;
238  }
239  }
240  break;
241  case nextGeneration_pass2: {
242  //--- this additional pass is for avoid to modify the
243  //--- genotypes contained in the last generation of the evolution
244  //--- and to allow to save the genotypes after each generation
245  Genome* old = genome();
246  setGenome( reprod->reproduction( old ) );
247  delete old;
248  fitfunc->setGenome( genome() );
249  evaluationDone = false;
250  setGeneration( generation()+1 );
251  for( int i=0; i<numThreadv; i++ ) {
252  evalThreads[i]->eval->initGeneration( generation() );
253  }
254  currPhase = initEvaluation;
255  }
256  break;
257  case endEvolution:
258  finalize();
259  break;
260  default:
261  qFatal( "Default switch in NSGA2::gaStep" );
262  break;
263  }
264 }
265 
267  // Set evaluation done, and check which phase to go to
268  evaluationDone = true;
269  if ( generation() < numGens ) {
270  currPhase = nextGeneration_pass2;
271  } else {
272  currPhase = endEvolution;
273  }
274 }
275 
277  if ( isFinalized && !isInitialized ) return;
278 
279  isInitialized = false;
280  isFinalized = true;
281  evolutionEnd = true;
282 }
283 
284 QVector<NSGA2::nsgaGenome> NSGA2::fastNonDominatedSort( nsgaGenome& pareto ) {
285  QMap<nsgaGenotype*, nsgaGenome> dominatedBy;
286  QVector<nsgaGenome> frontsByRank;
287  frontsByRank.resize(1);
288  //--- create the first front containing the top solutions
289  for( int p=0; p<pareto.size(); p++ ) {
290  //--- domination counter of genP reset to zero
291  pareto[p]->dominationCounter = 0;
292  for( int q=0; q<pareto.size(); q++ ) {
293  if ( p==q ) continue;
294  if ( pareto[q]->genotype->dominatedBy( pareto[p]->genotype ) ) {
295  // OPTIMIZE: it seems that most of the time is spent on accessing the QMap
296  dominatedBy[ pareto[p] ].append( pareto[q] );
297  } else if ( pareto[p]->genotype->dominatedBy( pareto[q]->genotype ) ) {
298  pareto[p]->dominationCounter++;
299  }
300  }
301  //--- if nP == 0 means that genP is a solution belongs to the top pareto-front
302  if ( pareto[p]->dominationCounter == 0 ) {
303  pareto[p]->rank = 0;
304  frontsByRank[0].append( pareto[p] );
305  }
306  }
307  //--- create all the other fronts
308  bool done = false;
309  int currentFront = 0;
310  while( !done ) {
311  nsgaGenome newFront;
312  for( int i=0; i<frontsByRank[currentFront].size(); i++ ) {
313  nsgaGenotype* p = frontsByRank[currentFront][i];
314  nsgaGenome pDominate = dominatedBy[p];
315  for( int q=0; q<pDominate.size(); q++ ) {
316  pDominate[q]->dominationCounter--;
317  if ( pDominate[q]->dominationCounter == 0 ) {
318  pDominate[q]->rank = currentFront+1;
319  newFront.append( pDominate[q] );
320  }
321  }
322  }
323  currentFront++;
324  if ( newFront.isEmpty() ) {
325  done = true;
326  } else {
327  frontsByRank.append( newFront );
328  }
329  }
330  int total = 0;
331  for( int i=0; i<frontsByRank.size(); i++ ) {
332  total += frontsByRank[i].size();
333  }
334  return frontsByRank;
335 }
336 
337 void NSGA2::crowdingDistanceAssignment( nsgaGenome& genome ) {
338  int dimGenome = genome.size();
339  if ( dimGenome == 0 ) return;
340  int numObjs = genome[0]->genotype->numOfObjectives();
341  //--- vectors containing the max and min values of objectives in this genome
342  QVector<double> fmax;
343  QVector<double> fmin;
344  fmax.resize( numObjs );
345  fmin.resize( numObjs );
346  for( int i=0; i<numObjs; i++ ) {
347  fmax[i] = genome[0]->genotype->objective( i );
348  fmin[i] = genome[0]->genotype->objective( i );
349  }
350  //--- initialize distance and calculate fmax and fmin values
351  for( int i=0; i<dimGenome; i++ ) {
352  genome[i]->distance = 0;
353  for( int m=0; m<numObjs; m++ ) {
354  fmax[m] = qMax( fmax[m], genome[i]->genotype->objective(m) );
355  fmin[m] = qMin( fmin[m], genome[i]->genotype->objective(m) );
356  }
357  }
358  //--- calculate the distance
359  nObjectiveGreaterThan objCompare;
360  for( int m=0; m<numObjs; m++ ) {
361  // currentObjective is used by nObjectiveGreaterThan for sorting
362  objCompare.currentObjective = m;
363  qStableSort( genome.begin(), genome.end(), objCompare );
364  // the maximum value is numObj, setting to numObj assure that this two
365  // genotypes are always the top in the current front
366  genome[0]->distance = numObjs; //DBL_MAX;
367  genome.last()->distance = numObjs; //DBL_MAX;
368  for( int i=1; i<dimGenome-1; i++ ) {
369  double m1 = genome[i+1]->genotype->objective(m);
370  double m2 = genome[i-1]->genotype->objective(m);
371  genome[i]->distance += fabs(m1-m2)/(fmax[m]-fmin[m]);
372  // if the value is nan, then it will setted to zero (worst distance)
373  if ( genome[i]->distance != genome[i]->distance ) {
374  genome[i]->distance = 0.0;
375  }
376  }
377  }
378 }
379 
380 NSGA2::evaluationThread::evaluationThread( NSGA2* p, Evaluation* eProto )
381  : parent(p), id(0), blocked(false) {
382  eval = eProto->clone();
383  eval->setGenome( p->genome() );
384  eval->setGA( p );
385 }
386 
387 NSGA2::evaluationThread::~evaluationThread() {
388  delete eval;
389  sequence.clear();
390 }
391 
392 void NSGA2::evaluationThread::runStep() {
393  if ( blocked ) {
394  return;
395  }
396 
397  parent->nextGeneration = false;
398  eval->evaluateStep();
399  if ( eval->isEvaluationDone() ) {
400  int nextIdSeq = idSeq + 1;
401  eval->finalize();
402  if ( nextIdSeq >= sequence.size() ) {
403  blocked = true;
404  return;
405  }
406  idSeq = nextIdSeq;
407  int nextId = sequence[ idSeq ];
408  id = nextId;
409  eval->initialize( parent->genome()->at( id ) );
410  }
411 }
412 
413 void NSGA2::runStepWrapper( NSGA2::evaluationThread* e ) {
414  e->runStep();
415 }
416 
417 } // end namespace farsa