ga/src/gas/nsga2.cpp

00001 /********************************************************************************
00002  *  FARSA Genetic Algorithm Library                                             *
00003  *  Copyright (C) 2007-2011 Gianluca Massera <emmegian@yahoo.it>                *
00004  *                                                                              *
00005  *  This program is free software; you can redistribute it and/or modify        *
00006  *  it under the terms of the GNU General Public License as published by        *
00007  *  the Free Software Foundation; either version 2 of the License, or           *
00008  *  (at your option) any later version.                                         *
00009  *                                                                              *
00010  *  This program is distributed in the hope that it will be useful,             *
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of              *
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               *
00013  *  GNU General Public License for more details.                                *
00014  *                                                                              *
00015  *  You should have received a copy of the GNU General Public License           *
00016  *  along with this program; if not, write to the Free Software                 *
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  *
00018  ********************************************************************************/
00019 
00020 #include "factory.h"
00021 #include "gas/nsga2.h"
00022 #include "core/reproduction.h"
00023 #include "core/genome.h"
00024 #include "core/evaluation.h"
00025 #include <cmath>
00026 #include <cfloat>
00027 #include <QThreadPool>
00028 #include <QtConcurrentMap>
00029 using namespace QtConcurrent;
00030 
00031 namespace farsa {
00032 
00033 NSGA2::NSGA2()
00034     : GeneticAlgo(), lastPareto() {
00035     fitfunc = 0;
00036     reprod = 0;
00037     numGens = 0;
00038     currPhase = initEvaluation;
00039     numThreadv = 1;
00040     isInitialized = false;
00041     isFinalized = true;
00042 }
00043 
00044 NSGA2::~NSGA2() {
00045     foreach( NSGA2::evaluationThread* e, evalThreads ) {
00046         delete e;
00047     }
00048     delete fitfunc;
00049 }
00050 
00051 void NSGA2::configure( ConfigurationParameters& params, QString prefix ) {
00052     setGenome( params.getObjectFromGroup<Genome>( prefix + QString( "GENOME" ) ) );
00053     setEvaluation( params.getObjectFromGroup<Evaluation>( prefix + QString( "EVALUATION" ) ) );
00054     fitfunc->setGenome( genome() );
00055     setReproduction( params.getObjectFromGroup<Reproduction>( prefix + QString( "REPRODUCTION" ) ) );
00056     setNumGenerations( params.getValue( prefix + QString( "ngenerations" ) ).toInt() );
00057     if ( numGenerations() == 0 ) {
00058         qWarning( "Setting number of Generations to ZERO!! Check your config file" );
00059     }
00060     setNumThreads( params.getValue( prefix + QString( "numThreads" ) ).toInt() );
00061 }
00062 
00063 void NSGA2::save( ConfigurationParameters& params, QString prefix ) {
00064     params.createParameter( prefix, QString("type"), "NSGA2" );
00065     params.createParameter( prefix, QString("numThreads"), QString("%1").arg( numThreads() ) );
00066     params.createParameter( prefix, QString("ngenerations"), QString("%1").arg( numGenerations() ) );
00067     //--- EVALUATION
00068     fitfunc->save( params, params.createSubGroup( prefix, "EVALUATION" ) );
00069     //--- REPRODUCTION
00070     reproduction()->save( params, params.createSubGroup( prefix, "REPRODUCTION" ) );
00071     //--- GENOME
00072     genome()->save( params, params.createSubGroup( prefix, "GENOME" ) );
00073 }
00074 
00075 void NSGA2::describe( QString type ) {
00076     Descriptor d = addTypeDescription( type, "Non-dominated Sorting Genetic Algorithm version 2" );
00077     d.describeInt( "numThreads" ).limits( 1, 32 ).help( "Number of threads to parallelize the evaluation of individuals" );
00078     d.describeInt( "ngenerations" ).limits( 1, INT_MAX ).def( 1000 ).help( "Number of the generations of the evolutionary process" );
00079     d.describeSubgroup( "EVALUATION" ).type( "Evaluation" ).props( IsMandatory ).help( "Object that calculate the fitness", "Create a subclass of Evalution and code your custom fitness function" );
00080     d.describeSubgroup( "REPRODUCTION" ).type( "Reproduction" ).props( IsMandatory ).help( "Object that generate the new generations" );
00081     d.describeSubgroup( "GENOME" ).type( "Genome" ).props( IsMandatory ).help( "Object containing the individuals under evolution" );
00082 }
00083 
00084 void NSGA2::setNumThreads( int numThreads ) {
00085     if ( numThreads < 1 ) {
00086         qWarning( "The number of Threads must be greater than one!!" );
00087     }
00088     Q_ASSERT_X( !isInitialized && isFinalized ,
00089             "NSGA2::setNumThreads",
00090             "This method can only called before initialize of NSGA2" );
00091     Q_ASSERT_X( fitfunc != 0 ,
00092             "NSGA2::setNumThreads",
00093             "This method must be called after an Evaluation object has been setted by NSGA2::setEvaluation" );
00094     numThreadv = numThreads;
00095     return;
00096 }
00097 
00098 int NSGA2::numThreads() {
00099     return numThreadv;
00100 }
00101 
00102 void NSGA2::setEvaluation( Evaluation* fitfunc ) {
00103     this->fitfunc = fitfunc;
00104     this->fitfunc->setGA( this );
00105 }
00106 
00107 Evaluation* NSGA2::evaluationPrototype()
00108 {
00109     return fitfunc;
00110 }
00111 
00112 QVector<Evaluation*> NSGA2::evaluationPool() {
00113     QVector<Evaluation*> ev;
00114     foreach( NSGA2::evaluationThread* e, evalThreads ) {
00115         ev.append( e->eval );
00116     }
00117     return ev;
00118 }
00119 
00120 void NSGA2::setReproduction( Reproduction* reprod ) {
00121     this->reprod = reprod;
00122     this->reprod->setGA( this );
00123 }
00124 
00125 Reproduction* NSGA2::reproduction() {
00126     return reprod;
00127 }
00128 
00129 void NSGA2::initialize() {
00130     if ( isInitialized && !isFinalized ) return;
00131     Q_ASSERT_X( fitfunc != 0 ,
00132             "NSGA2::initialize",
00133             "You have to setup the Evaluation object of NSGA2 (Fitness Function)" );
00134     Q_ASSERT_X( reprod !=0 ,
00135             "NSGA2::initialize",
00136             "You have to setup the Reproduction operator of NSGA2" );
00137 
00138     isInitialized = true;
00139     isFinalized = false;
00140     setGeneration( 0 );
00141     currPhase = initEvaluation;
00142     evolutionEnd = false;
00143     evaluationDone = false;
00144     //--- Setting up the evalThreads
00145     for( int i=0; i<evalThreads.size(); i++ ) {
00146         delete (evalThreads[i]);
00147     }
00148     evalThreads.clear();
00149     for( int i=0; i<numThreadv; i++ ) {
00150         evalThreads.append( new evaluationThread( this, fitfunc ) );
00151     }
00152     //--- set the number of thread to create
00153     QThreadPool::globalInstance()->setMaxThreadCount( numThreadv );
00154 }
00155 
00156 void NSGA2::gaStep() {
00157     switch( currPhase ) {
00158     case initEvaluation:
00159         for( int i=0; i<numThreadv; i++ ) {
00160             evalThreads[i]->sequence.clear();
00161         }
00162         for( int i=0; i<(int)genome()->size(); i++ ) {
00163             evalThreads[ i%numThreadv ]->sequence.append( i );
00164         }
00165         for( int i=0; i<numThreadv; i++ ) {
00166             evalThreads[i]->idSeq = 0;
00167             evalThreads[i]->id = evalThreads[i]->sequence[ evalThreads[i]->idSeq ];
00168             evalThreads[i]->eval->setGenome( genome() );
00169             evalThreads[i]->eval->initialize( genome()->at( evalThreads[i]->id ) );
00170             evalThreads[i]->blocked = false;
00171         }
00172         currPhase = evaluating;
00173         break;
00174     case evaluating: { /* Multi Thread Block (i.e. Parallel Evaluation of Genotypes */
00175         nextGeneration = true;
00176         if ( numThreadv == 1 ) {
00177             // Don't use Threads if is not necessary
00178             evalThreads[0]->runStep();
00179         } else {
00180             QFuture<void> future = map( evalThreads, NSGA2::runStepWrapper );
00181             future.waitForFinished();
00182         }
00183         if ( nextGeneration ) {
00184             currPhase = nextGeneration_pass1;
00185         }
00186         } /* End of Multi Thread Block */
00187         break;
00188     case nextGeneration_pass1: {
00189         // --- Core of the NSGA-II Algorithm
00190         //   -1) merge previous front population lastPareto with current genome()
00191         //--- this merge assure that the best (elite) pareto-fronts are mantained
00192         nsgaGenome allGenome;
00193         for( unsigned int i=0; i<lastPareto.size(); i++ ) {
00194             allGenome.append( new nsgaGenotype( lastPareto.at(i), 0, 0.0 ) );
00195         }
00196         for( unsigned int i=0; i<genome()->size(); i++ ) {
00197             allGenome.append( new nsgaGenotype( genome()->at(i), 0, 0.0 ) );
00198         }
00199         //   -2) fastNonDominatedSort( onMergedPopulation )
00200         //--- genotype grouped by the front rank
00201         QVector<nsgaGenome> frontsByRank = fastNonDominatedSort( allGenome );
00202         //   -3) create new population of size genome().size() using the fronts
00203         //       calculated and use the crowdingDistanceAssignment
00204         unsigned int currentGenotype = 0;
00205         int numOfFronts = frontsByRank.size();
00206         int numObjs = allGenome[0]->genotype->numOfObjectives();
00207         for( int front=0; front<numOfFronts; front++ ) {
00208             //--- sort it on crowding distance and add to the new Genome
00209             crowdingDistanceAssignment( frontsByRank[front] );
00210             qStableSort( frontsByRank[front].begin(), frontsByRank[front].end(), NSGA2::crowdingDistanceGreaterThan );
00211             foreach( nsgaGenotype* gen, frontsByRank[front] ) {
00212                 //--- assign the rank
00213                 gen->genotype->setRank( 2*(numOfFronts - front) + (gen->distance/numObjs) );
00214                 genome()->set( currentGenotype, gen->genotype );
00215                 currentGenotype++;
00216                 if ( currentGenotype == genome()->size() ) break;
00217             }
00218             if ( currentGenotype == genome()->size() ) break;
00219         }
00220         //   -4) lastPareto <- genome()
00221         //--- this pass correspond to elite the pareto-fronts
00222         lastPareto = *(genome());
00223         //   -5) clean up memory
00224         for( int i=0; i<allGenome.size(); i++ ) {
00225             delete (allGenome[i]);
00226         }
00227         updateStats();
00228         evaluationDone = true;
00229         if ( generation() < numGens ) {
00230             currPhase = nextGeneration_pass2;
00231         } else {
00232             currPhase = endEvolution;
00233         }
00234         }
00235         break;
00236     case nextGeneration_pass2: {
00237         //--- this additional pass is for avoid to modify the
00238         //--- genotypes contained in the last generation of the evolution
00239         //--- and to allow to save the genotypes after each generation
00240         Genome* old = genome();
00241         setGenome( reprod->reproduction( old ) );
00242         delete old;
00243         fitfunc->setGenome( genome() );
00244         evaluationDone = false;
00245         setGeneration( generation()+1 );
00246         currPhase = initEvaluation;
00247         }
00248         break;
00249     case endEvolution:
00250         finalize();
00251         break;
00252     default:
00253         qFatal( "Default switch in NSGA2::gaStep" );
00254         break;
00255     }
00256 }
00257 
00258 void NSGA2::skipEvaluation() {
00259     // Set evaluation done, and check which phase to go to
00260     evaluationDone = true;
00261     if ( generation() < numGens ) {
00262         currPhase = nextGeneration_pass2;
00263     } else {
00264         currPhase = endEvolution;
00265     }
00266 }
00267 
00268 void NSGA2::finalize() {
00269     if ( isFinalized && !isInitialized ) return;
00270 
00271     isInitialized = false;
00272     isFinalized = true;
00273     evolutionEnd = true;
00274 }
00275 
00276 QVector<NSGA2::nsgaGenome> NSGA2::fastNonDominatedSort( nsgaGenome& pareto ) {
00277     QMap<nsgaGenotype*, nsgaGenome> dominatedBy;
00278     QVector<nsgaGenome> frontsByRank;
00279     frontsByRank.resize(1);
00280     //--- create the first front containing the top solutions
00281     for( int p=0; p<pareto.size(); p++ ) {
00282         //--- domination counter of genP reset to zero
00283         pareto[p]->dominationCounter = 0;
00284         for( int q=0; q<pareto.size(); q++ ) {
00285             if ( p==q ) continue;
00286             if ( pareto[q]->genotype->dominatedBy( pareto[p]->genotype ) ) {
00287                 // OPTIMIZE: it seems that most of the time is spent on accessing the QMap
00288                 dominatedBy[ pareto[p] ].append( pareto[q] );
00289             } else if ( pareto[p]->genotype->dominatedBy( pareto[q]->genotype ) ) {
00290                 pareto[p]->dominationCounter++;
00291             }
00292         }
00293         //--- if nP == 0 means that genP is a solution belongs to the top pareto-front
00294         if ( pareto[p]->dominationCounter == 0 ) {
00295             pareto[p]->rank = 0;
00296             frontsByRank[0].append( pareto[p] );
00297         }
00298     }
00299     //--- create all the other fronts
00300     bool done = false;
00301     int currentFront = 0;
00302     while( !done ) {
00303         nsgaGenome newFront;
00304         for( int i=0; i<frontsByRank[currentFront].size(); i++ ) {
00305             nsgaGenotype* p = frontsByRank[currentFront][i];
00306             nsgaGenome pDominate = dominatedBy[p];
00307             for( int q=0; q<pDominate.size(); q++ ) {
00308                 pDominate[q]->dominationCounter--;
00309                 if ( pDominate[q]->dominationCounter == 0 ) {
00310                     pDominate[q]->rank = currentFront+1;
00311                     newFront.append( pDominate[q] );
00312                 }
00313             }
00314         }
00315         currentFront++;
00316         if ( newFront.isEmpty() ) {
00317             done = true;
00318         } else {
00319             frontsByRank.append( newFront );
00320         }
00321     }
00322     int total = 0;
00323     for( int i=0; i<frontsByRank.size(); i++ ) {
00324         total += frontsByRank[i].size();
00325     }
00326     return frontsByRank;
00327 }
00328 
00329 void NSGA2::crowdingDistanceAssignment( nsgaGenome& genome ) {
00330     int dimGenome = genome.size();
00331     if ( dimGenome == 0 ) return;
00332     int numObjs = genome[0]->genotype->numOfObjectives();
00333     //--- vectors containing the max and min values of objectives in this genome
00334     QVector<double> fmax;
00335     QVector<double> fmin;
00336     fmax.resize( numObjs );
00337     fmin.resize( numObjs );
00338     for( int i=0; i<numObjs; i++ ) {
00339         fmax[i] = genome[0]->genotype->objective( i );
00340         fmin[i] = genome[0]->genotype->objective( i );
00341     }
00342     //--- initialize distance and calculate fmax and fmin values
00343     for( int i=0; i<dimGenome; i++ ) {
00344         genome[i]->distance = 0;
00345         for( int m=0; m<numObjs; m++ ) {
00346             fmax[m] = qMax( fmax[m], genome[i]->genotype->objective(m) );
00347             fmin[m] = qMin( fmin[m], genome[i]->genotype->objective(m) );
00348         }
00349     }
00350     //--- calculate the distance
00351     nObjectiveGreaterThan objCompare;
00352     for( int m=0; m<numObjs; m++ ) {
00353         // currentObjective is used by nObjectiveGreaterThan for sorting
00354         objCompare.currentObjective = m;
00355         qStableSort( genome.begin(), genome.end(), objCompare );
00356         // the maximum value is numObj, setting to numObj assure that this two
00357         // genotypes are always the top in the current front
00358         genome[0]->distance = numObjs; //DBL_MAX;
00359         genome.last()->distance = numObjs; //DBL_MAX;
00360         for( int i=1; i<dimGenome-1; i++ ) {
00361             double m1 = genome[i+1]->genotype->objective(m);
00362             double m2 = genome[i-1]->genotype->objective(m);
00363             genome[i]->distance += fabs(m1-m2)/(fmax[m]-fmin[m]);
00364             // if the value is nan, then it will setted to zero (worst distance)
00365             if ( genome[i]->distance != genome[i]->distance ) {
00366                 genome[i]->distance = 0.0;
00367             }
00368         }
00369     }
00370 }
00371 
00372 NSGA2::evaluationThread::evaluationThread( NSGA2* p, Evaluation* eProto )
00373     : parent(p), id(0), blocked(false) {
00374     eval = eProto->clone();
00375     eval->setGenome( p->genome() );
00376     eval->setGA( p );
00377 }
00378 
00379 NSGA2::evaluationThread::~evaluationThread() {
00380     delete eval;
00381     sequence.clear();
00382 }
00383 
00384 void NSGA2::evaluationThread::runStep() {
00385     if ( blocked ) {
00386         return;
00387     }
00388     
00389     parent->nextGeneration = false;
00390     eval->evaluateStep();
00391     if ( eval->isEvaluationDone() ) {
00392         int nextIdSeq = idSeq + 1;
00393         eval->finalize();
00394         if ( nextIdSeq >= sequence.size() ) {
00395             blocked = true;
00396             return;
00397         }
00398         idSeq = nextIdSeq;
00399         int nextId = sequence[ idSeq ];
00400         id = nextId;
00401         eval->initialize( parent->genome()->at( id ) );
00402     }
00403 }
00404 
00405 void NSGA2::runStepWrapper( NSGA2::evaluationThread* e ) {
00406     e->runStep();
00407 }
00408 
00409 } // end namespace farsa