ga/src/gas/laralga.cpp

00001 /********************************************************************************
00002  *  FARSA Genetic Algorithm Library                                             *
00003  *  Copyright (C) 2007-2008 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 "gas/laralga.h"
00021 #include "core/mutation.h"
00022 #include "core/genome.h"
00023 #include "evaluations/multitrials.h"
00024 #include "factory.h"
00025 #include <QThreadPool>
00026 #include <QtConcurrentMap>
00027 using namespace QtConcurrent;
00028 
00029 namespace farsa {
00030 
00031 LaralGA::LaralGA()
00032     : GeneticAlgo() {
00033     fitfunc = 0;
00034     muta = 0;
00035     currGenotype = 0;
00036     numGens = 0;
00037     nreproducing = 0;
00038     noffspring = 0;
00039     nelitism = 0;
00040     elitismEnabled = 0;
00041     currPhase = initEvaluation;
00042     numThreadv = 1;
00043     isInitialized = false;
00044     isFinalized = true;
00045 }
00046 
00047 LaralGA::~LaralGA() {
00048     //--- nothing to do
00049 }
00050 
00051 void LaralGA::configure( ConfigurationParameters& params, QString prefix ) {
00052     setGenome( params.getObjectFromGroup<Genome>( prefix + QString( "GENOME" ) ) );
00053     setFitnessFunction( params.getObjectFromGroup<MultiTrials>( prefix + QString( "EVALUATION" ) ) );
00054     fitfunc->setGenome( genome() );
00055     setMutation( params.getObjectFromGroup<Mutation>( prefix + QString("REPRODUCTION/MUTATION") ) );
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     const QString reproductionGroup( prefix + QString( "REPRODUCTION" ) + ConfigurationParameters::GroupSeparator() );
00061     int nreproduce = params.getValue( reproductionGroup + QString( "nreproducing" ) ).toInt();
00062     bool useElitism = ! (params.getValue( reproductionGroup + QString( "elitism" ) ).compare( "true", Qt::CaseInsensitive));
00063     int nelitism = params.getValue( reproductionGroup + QString( "nelited" ) ).toInt();
00064     setReproduceParams( nreproduce, useElitism, nelitism );
00065     setNumThreads( params.getValue( prefix + QString( "numThreads" ) ).toInt() );
00066 }
00067 
00068 void LaralGA::save( ConfigurationParameters& params, QString prefix ) {
00069     params.createParameter( prefix, QString("type"), "LaralGA" );
00070     params.createParameter( prefix, QString("numThreads"), QString("%1").arg(numThreads()) );
00071     params.createParameter( prefix, QString("ngenerations"), QString("%1").arg( numGenerations() ) );
00072     //--- EVALUATION
00073     fitnessFunction()->save( params, params.createSubGroup( prefix, "EVALUATION" ) );
00074     //--- REPRODUCTION
00075     QString reproductionGroup = params.createSubGroup( prefix, "REPRODUCTION" );
00076     params.createParameter( reproductionGroup, "nreproducing", QString("%1").arg( numReproducing() ) );
00077     params.createParameter( reproductionGroup, "elitism", isElitismEnabled() ? "true" : "false" );
00078     params.createParameter( reproductionGroup, "nelited", QString("%1").arg( numElitism() ) );
00079     mutation()->save( params, params.createSubGroup( reproductionGroup, "MUTATION" ) );
00080     //--- GENOME
00081     genome()->save( params, params.createSubGroup( prefix, "GENOME" ) );
00082 }
00083 
00084 void LaralGA::describe( QString type ) {
00085     Descriptor d = addTypeDescription( type, "A simple Genetic Algorithm used at LARAL laboratory", "This Genetic Algorithm runs more than one replica of the evolutionary process each of them use a reproduction schema without crossover and with a deterministic rank selection" );
00086     d.describeInt( "numThreads" ).limits( 1, 32 ).help( "Number of threads to parallelize the evaluation of individuals" );
00087     d.describeInt( "ngenerations" ).limits( 1, INT_MAX ).props( IsMandatory ).help( "Number of generations per evolutionary process" );
00088     d.describeSubgroup( "EVALUATION" ).type( "MultiTrials" ).props( IsMandatory ).help( "Object that calculate the fitness", "Create a subclass of MultiTrials and code your custom fitness function" );
00089     SubgroupDescriptor r = d.describeSubgroup( "REPRODUCTION" ).props( IsMandatory ).help( "Paramenters that affect the reproduction process" );
00090         r.describeInt( "nreproducing" ).limits( 1, INT_MAX ).def(10).props( IsMandatory ).help( "Number of the most fitted individual to reproduce" );
00091         r.describeBool( "elitism" ).def( false ).help( "Enable the elitism", "When true, the nelited most fitted individual will be copied exactly (without mutation) to the new generations" );
00092         r.describeInt( "nelited" ).limits( 1, INT_MAX ).def( 10 ).help( "Number of individual to copy exactly to the new generation" );
00093         r.describeSubgroup( "MUTATION" ).type( "Mutation" ).props( IsMandatory ).help( "The type of mutation to use" );
00094     d.describeSubgroup( "GENOME" ).type( "Genome" ).props( IsMandatory ).help( "The genome containing the individual under evolution" );
00095 }
00096 
00097 void LaralGA::setNumThreads( int numThreads ) {
00098     if ( numThreads < 1 ) {
00099         qWarning( "The number of Threads must be greater than one!!" );
00100     }
00101     Q_ASSERT_X( !isInitialized && isFinalized ,
00102             "LaralGA::setNumThreads",
00103             "This method can only called before initialize of LaralGA" );
00104     Q_ASSERT_X( fitfunc != 0 ,
00105             "LaralGA::setNumThreads",
00106             "This method must be called after a MultiTrials object has been setted by LaralGA::setFitnessFunction" );
00107     numThreadv = numThreads;
00108     return;
00109 }
00110 
00111 int LaralGA::numThreads() {
00112     return numThreadv;
00113 }
00114 
00115 void LaralGA::setFitnessFunction( MultiTrials* fitfunc ) {
00116     this->fitfunc = fitfunc;
00117     this->fitfunc->setGA( this );
00118 }
00119 
00120 Evaluation* LaralGA::evaluationPrototype()
00121 {
00122     return fitfunc;
00123 }
00124 
00125 QVector<Evaluation*> LaralGA::evaluationPool() {
00126     QVector<Evaluation*> ev;
00127     foreach( LaralGA::evaluationThread* e, evalThreads ) {
00128         ev.append( e->eval );
00129     }
00130     return ev;
00131 }
00132 
00133 MultiTrials* LaralGA::fitnessFunction() {
00134     return fitfunc;
00135 }
00136 
00137 void LaralGA::setMutation( Mutation* mutate ) {
00138     this->muta = mutate;
00139     this->muta->setGA( this );
00140 }
00141 
00142 Mutation* LaralGA::mutation() {
00143     return muta;
00144 }
00145 
00146 void LaralGA::setReproduceParams( int nreproducing, bool useElitism, int nelitism ) {
00147     this->nreproducing = nreproducing;
00148     elitismEnabled = useElitism;
00149     this->nelitism = nelitism;
00150     noffspring = genomev->size()/nreproducing;
00151     //--- check if parameters are setted correctly
00152     Q_ASSERT_X( noffspring * nreproducing == (int)(genomev->size()),
00153                 "LaralGA::setReproduceParams",
00154                 QString( "nreproducing must be divisible by genome dimension: %1 %2 %3" ).arg(noffspring).arg(nreproducing).arg(genomev->size()).toAscii().data() );
00155     if ( useElitism ) {
00156         Q_ASSERT_X( useElitism && ( nelitism <= nreproducing ),
00157                 "LaralGA::setReproduceParams",
00158                 "The number of elited genotype must be less or equal to selected ones" );
00159     }
00160 }
00161 
00162 int LaralGA::numReproducing() {
00163     return nreproducing;
00164 }
00165 
00166 int LaralGA::numOffspring() {
00167     return noffspring;
00168 }
00169 
00170 int LaralGA::numElitism() {
00171     return nelitism;
00172 }
00173 
00174 bool LaralGA::isElitismEnabled() {
00175     return elitismEnabled;
00176 }
00177 
00178 void LaralGA::initialize() {
00179     if ( isInitialized && !isFinalized ) return;
00180     Q_ASSERT_X( fitfunc != 0 ,
00181             "LaralGA::initialize",
00182             "You have to setup the FitnessFunction of LaralGA" );
00183     Q_ASSERT_X( muta !=0 ,
00184             "LaralGA::initialize",
00185             "You have to setup the Mutate operator of LaralGA" );
00186 
00187     isInitialized = true;
00188     isFinalized = false;
00189     currGenotype = 0;
00190     setGeneration( 0 );
00191     currPhase = initEvaluation;
00192     evolutionEnd = false;
00193     evaluationDone = false;
00194     //--- Setting up the evalThreads
00195     for( int i=0; i<evalThreads.size(); i++ ) {
00196         delete (evalThreads[i]);
00197     }
00198     evalThreads.clear();
00199     for( int i=0; i<numThreadv; i++ ) {
00200         evalThreads.append( new evaluationThread( this, fitfunc ) );
00201     }
00202     //--- set the number of thread to create
00203     QThreadPool::globalInstance()->setMaxThreadCount( numThreadv );
00204 }
00205 
00206 void LaralGA::gaStep() {
00207     switch( currPhase ) {
00208     case initEvaluation:
00209         for( int i=0; i<numThreadv; i++ ) {
00210             evalThreads[i]->sequence.clear();
00211         }
00212         for( int i=0; i<(int)genome()->size(); i++ ) {
00213             evalThreads[ i%numThreadv ]->sequence.append( i );
00214         }
00215         for( int i=0; i<numThreadv; i++ ) {
00216             evalThreads[i]->idSeq = 0;
00217             evalThreads[i]->id = evalThreads[i]->sequence[ evalThreads[i]->idSeq ];
00218             evalThreads[i]->eval->setGenome( genome() );
00219             evalThreads[i]->eval->initialize( genome()->at( evalThreads[i]->id ) );
00220             evalThreads[i]->blocked = false;
00221         }
00222         currPhase = evaluating;
00223         break;
00224     case evaluating: { /* Multi Thread Block (i.e. Parallel Evaluation of Genotypes */
00225         nextGeneration = true;
00226         if ( numThreadv == 1 ) {
00227             // Don't use Threads if is not necessary
00228             evalThreads[0]->runStep();
00229         } else {
00230             QFuture<void> future = map( evalThreads, LaralGA::runStepWrapper );
00231             future.waitForFinished();
00232         }
00233         if ( nextGeneration ) {
00234             currPhase = nextGeneration_pass1;
00235         }
00236         } /* End of Multi Thread Block */
00237         break;
00238     case nextGeneration_pass1:
00239         qSort( genome()->begin(), genome()->end(), Genotype::rankGreaterThanComparator );
00240         updateStats();
00241         evaluationDone = true;
00242         if ( generation() < numGens ) {
00243             currPhase = nextGeneration_pass2;
00244         } else {
00245             currPhase = endEvolution;
00246         }
00247         break;
00248     case nextGeneration_pass2:
00249         //--- this additional pass is for avoid to modify the
00250         //--- genotypes contained in the last generation of the evolution
00251         //--- and to allow to save the genotypes after each generation
00252         setGeneration( generation()+1 );
00253         evaluationDone = false;
00254         createNextGeneration();
00255         currPhase = initEvaluation;
00256         break;
00257     case endEvolution:
00258         finalize();
00259         qWarning( "Finish Evolution" );
00260         break;
00261     default:
00262         qFatal( "Default switch in LaralGA::gaStep" );
00263         break;
00264     }
00265 }
00266 
00267 void LaralGA::skipEvaluation() {
00268     // Set evaluation done, and check which phase to go to
00269     evaluationDone = true;
00270     if ( generation() < numGens ) {
00271         currPhase = nextGeneration_pass2;
00272     } else {
00273         currPhase = endEvolution;
00274     }
00275 }
00276 
00277 void LaralGA::finalize() {
00278     if ( isFinalized && !isInitialized ) return;
00279 
00280     isInitialized = false;
00281     isFinalized = true;
00282     evolutionEnd = true;
00283 }
00284 
00285 void LaralGA::createNextGeneration() {
00286     Genome& genref = *(genome());
00287     muta->setGenome( genome() );
00288     for( int i=0; i<nreproducing; i++ ) {
00289         //--- skip for now the first nreproducing position
00290         for( int j=1; j<noffspring; j++ ) {
00291             //--- id of the current genome to replace
00292             int id = i + j*nreproducing;
00293             //--- clone and mutate the i-th selected individual
00294             genref[id]->assign( genref[i] );
00295             muta->mutate( genref[id] );
00296         }
00297     }
00298     //--- also mutate the selected, excepted for the elited ones
00299     int startId = 0;
00300     if ( elitismEnabled ) {
00301         startId = nelitism;
00302     }
00303     for( int i=startId; i<nreproducing; i++ ) {
00304         muta->mutate( genref[i] );
00305     }
00306 }
00307 
00308 LaralGA::evaluationThread::evaluationThread( LaralGA* p, MultiTrials* eProto )
00309     : parent(p), id(0), blocked(false) {
00310     eval = eProto->clone();
00311     eval->setGenome( p->genome() );
00312     eval->setGA( p );
00313 }
00314 
00315 LaralGA::evaluationThread::~evaluationThread() {
00316     delete eval;
00317     sequence.clear();
00318 }
00319 
00320 void LaralGA::evaluationThread::runStep() {
00321     if ( blocked ) {
00322         return;
00323     }
00324     
00325     parent->nextGeneration = false;
00326     eval->evaluateStep();
00327     if ( eval->isEvaluationDone() ) {
00328         int nextIdSeq = idSeq + 1;
00329         eval->finalize();
00330         eval->genotype()->setRank( eval->genotype()->fitness() );
00331         if ( nextIdSeq >= sequence.size() ) {
00332             blocked = true;
00333             return;
00334         }
00335         idSeq = nextIdSeq;
00336         int nextId = sequence[ idSeq ];
00337         id = nextId;
00338         eval->initialize( parent->genome()->at( id ) );
00339     }
00340 }
00341 
00342 void LaralGA::runStepWrapper( LaralGA::evaluationThread* e ) {
00343     e->runStep();
00344 }
00345 
00346 } // end namespace farsa