laralga.cpp
1 /********************************************************************************
2  * FARSA Genetic Algorithm Library *
3  * Copyright (C) 2007-2008 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 "gas/laralga.h"
21 #include "core/mutation.h"
22 #include "core/genome.h"
23 #include "evaluations/multitrials.h"
24 #include "factory.h"
25 #include "configurationhelper.h"
26 #include <QThreadPool>
27 #include <QtConcurrentMap>
28 using namespace QtConcurrent;
29 
30 namespace farsa {
31 
32 LaralGA::LaralGA()
33  : GeneticAlgo() {
34  fitfunc = 0;
35  muta = 0;
36  currGenotype = 0;
37  numGens = 0;
38  nreproducing = 0;
39  noffspring = 0;
40  nelitism = 0;
41  elitismEnabled = 0;
42  currPhase = initEvaluation;
43  numThreadv = 1;
44  isInitialized = false;
45  isFinalized = true;
46 }
47 
49  //--- nothing to do
50 }
51 
52 void LaralGA::configure( ConfigurationParameters& params, QString prefix ) {
53  setGenome( params.getObjectFromGroup<Genome>( prefix + QString( "GENOME" ) ) );
54  setFitnessFunction( params.getObjectFromGroup<MultiTrials>( prefix + QString( "EVALUATION" ) ) );
55  fitfunc->setGenome( genome() );
56  setMutation( params.getObjectFromGroup<Mutation>( prefix + QString("REPRODUCTION/MUTATION") ) );
57  setNumGenerations( ConfigurationHelper::getInt( params, prefix + QString( "ngenerations" ), 1000 ) );
58  const QString reproductionGroup( prefix + QString( "REPRODUCTION" ) + ConfigurationParameters::GroupSeparator() );
59  int nreproduce = params.getValue( reproductionGroup + QString( "nreproducing" ) ).toInt();
60  bool useElitism = ! (params.getValue( reproductionGroup + QString( "elitism" ) ).compare( "true", Qt::CaseInsensitive));
61  int nelitism = params.getValue( reproductionGroup + QString( "nelited" ) ).toInt();
62  setReproduceParams( nreproduce, useElitism, nelitism );
63  setNumThreads( ConfigurationHelper::getInt( params, prefix + QString( "numThreads" ), 1 ) );
64 }
65 
66 void LaralGA::save( ConfigurationParameters& params, QString prefix ) {
67  params.createParameter( prefix, QString("type"), "LaralGA" );
68  params.createParameter( prefix, QString("numThreads"), QString("%1").arg(numThreads()) );
69  params.createParameter( prefix, QString("ngenerations"), QString("%1").arg( numGenerations() ) );
70  //--- EVALUATION
71  fitnessFunction()->save( params, params.createSubGroup( prefix, "EVALUATION" ) );
72  //--- REPRODUCTION
73  QString reproductionGroup = params.createSubGroup( prefix, "REPRODUCTION" );
74  params.createParameter( reproductionGroup, "nreproducing", QString("%1").arg( numReproducing() ) );
75  params.createParameter( reproductionGroup, "elitism", isElitismEnabled() ? "true" : "false" );
76  params.createParameter( reproductionGroup, "nelited", QString("%1").arg( numElitism() ) );
77  mutation()->save( params, params.createSubGroup( reproductionGroup, "MUTATION" ) );
78  //--- GENOME
79  genome()->save( params, params.createSubGroup( prefix, "GENOME" ) );
80 }
81 
82 void LaralGA::describe( QString type ) {
83  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" );
84  d.describeInt( "numThreads" ).limits( 1, 32 ).def(1).help( "Number of threads to parallelize the evaluation of individuals" );
85  d.describeInt( "ngenerations" ).limits( 1, INT_MAX ).props( IsMandatory ).help( "Number of generations per evolutionary process" );
86  d.describeSubgroup( "EVALUATION" ).type( "MultiTrials" ).props( IsMandatory ).help( "Object that calculate the fitness", "Create a subclass of MultiTrials and code your custom fitness function" );
87  SubgroupDescriptor r = d.describeSubgroup( "REPRODUCTION" ).props( IsMandatory ).help( "Paramenters that affect the reproduction process" );
88  r.describeInt( "nreproducing" ).limits( 1, INT_MAX ).def(10).props( IsMandatory ).help( "Number of the most fitted individual to reproduce" );
89  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" );
90  r.describeInt( "nelited" ).limits( 1, INT_MAX ).def( 10 ).help( "Number of individual to copy exactly to the new generation" );
91  r.describeSubgroup( "MUTATION" ).type( "Mutation" ).props( IsMandatory ).help( "The type of mutation to use" );
92  d.describeSubgroup( "GENOME" ).type( "Genome" ).props( IsMandatory ).help( "The genome containing the individual under evolution" );
93 }
94 
95 void LaralGA::setNumThreads( int numThreads ) {
96  if ( numThreads < 1 ) {
97  qWarning( "The number of Threads must be greater than one!!" );
98  }
99  Q_ASSERT_X( !isInitialized && isFinalized ,
100  "LaralGA::setNumThreads",
101  "This method can only called before initialize of LaralGA" );
102  Q_ASSERT_X( fitfunc != 0 ,
103  "LaralGA::setNumThreads",
104  "This method must be called after a MultiTrials object has been setted by LaralGA::setFitnessFunction" );
105  numThreadv = numThreads;
106  return;
107 }
108 
110  return numThreadv;
111 }
112 
114  this->fitfunc = fitfunc;
115  this->fitfunc->setGA( this );
116 }
117 
119 {
120  return fitfunc;
121 }
122 
123 QVector<Evaluation*> LaralGA::evaluationPool() {
124  QVector<Evaluation*> ev;
125  foreach( LaralGA::evaluationThread* e, evalThreads ) {
126  ev.append( e->eval );
127  }
128  return ev;
129 }
130 
132  return fitfunc;
133 }
134 
136  this->muta = mutate;
137  this->muta->setGA( this );
138 }
139 
141  return muta;
142 }
143 
144 void LaralGA::setReproduceParams( int nreproducing, bool useElitism, int nelitism ) {
145  this->nreproducing = nreproducing;
146  elitismEnabled = useElitism;
147  this->nelitism = nelitism;
149  //--- check if parameters are setted correctly
150  Q_ASSERT_X( noffspring * nreproducing == (int)(genomev->size()),
151  "LaralGA::setReproduceParams",
152  QString( "nreproducing must be divisible by genome dimension: %1 %2 %3" ).arg(noffspring).arg(nreproducing).arg(genomev->size()).toAscii().data() );
153  if ( useElitism ) {
154  Q_ASSERT_X( useElitism && ( nelitism <= nreproducing ),
155  "LaralGA::setReproduceParams",
156  "The number of elited genotype must be less or equal to selected ones" );
157  }
158 }
159 
161  return nreproducing;
162 }
163 
165  return noffspring;
166 }
167 
169  return nelitism;
170 }
171 
173  return elitismEnabled;
174 }
175 
177  if ( isInitialized && !isFinalized ) return;
178  Q_ASSERT_X( fitfunc != 0 ,
179  "LaralGA::initialize",
180  "You have to setup the FitnessFunction of LaralGA" );
181  Q_ASSERT_X( muta !=0 ,
182  "LaralGA::initialize",
183  "You have to setup the Mutate operator of LaralGA" );
184 
185  isInitialized = true;
186  isFinalized = false;
187  currGenotype = 0;
188  setGeneration( 0 );
189  currPhase = initEvaluation;
190  evolutionEnd = false;
191  evaluationDone = false;
192  //--- Setting up the evalThreads
193  for( int i=0; i<evalThreads.size(); i++ ) {
194  delete (evalThreads[i]);
195  }
196  evalThreads.clear();
197  for( int i=0; i<numThreadv; i++ ) {
198  evalThreads.append( new evaluationThread( this, fitfunc ) );
199  evalThreads.last()->eval->initGeneration(0);
200  }
201  //--- set the number of thread to create
202  QThreadPool::globalInstance()->setMaxThreadCount( numThreadv );
203 }
204 
206  switch( currPhase ) {
207  case initEvaluation:
208  for( int i=0; i<numThreadv; i++ ) {
209  evalThreads[i]->sequence.clear();
210  }
211  for( int i=0; i<(int)genome()->size(); i++ ) {
212  evalThreads[ i%numThreadv ]->sequence.append( i );
213  }
214  for( int i=0; i<numThreadv; i++ ) {
215  evalThreads[i]->idSeq = 0;
216  evalThreads[i]->id = evalThreads[i]->sequence[ evalThreads[i]->idSeq ];
217  evalThreads[i]->eval->setGenome( genome() );
218  evalThreads[i]->eval->initialize( genome()->at( evalThreads[i]->id ) );
219  evalThreads[i]->blocked = false;
220  }
221  currPhase = evaluating;
222  break;
223  case evaluating: { /* Multi Thread Block (i.e. Parallel Evaluation of Genotypes */
224  nextGeneration = true;
225  if ( numThreadv == 1 ) {
226  // Don't use Threads if is not necessary
227  evalThreads[0]->runStep();
228  } else {
229  QFuture<void> future = map( evalThreads, LaralGA::runStepWrapper );
230  future.waitForFinished();
231  }
232  if ( nextGeneration ) {
233  currPhase = nextGeneration_pass1;
234  }
235  } /* End of Multi Thread Block */
236  break;
237  case nextGeneration_pass1:
238  qSort( genome()->begin(), genome()->end(), Genotype::rankGreaterThanComparator );
239  updateStats();
240  evaluationDone = true;
241  for( int i=0; i<numThreadv; i++ ) {
242  evalThreads[i]->eval->endGeneration( generation() );
243  }
244  if ( generation() < numGens ) {
245  currPhase = nextGeneration_pass2;
246  } else {
247  currPhase = endEvolution;
248  }
249  break;
250  case nextGeneration_pass2:
251  //--- this additional pass is for avoid to modify the
252  //--- genotypes contained in the last generation of the evolution
253  //--- and to allow to save the genotypes after each generation
254  setGeneration( generation()+1 );
255  evaluationDone = false;
256  createNextGeneration();
257  for( int i=0; i<numThreadv; i++ ) {
258  evalThreads[i]->eval->initGeneration( generation() );
259  }
260  currPhase = initEvaluation;
261  break;
262  case endEvolution:
263  finalize();
264  qWarning( "Finish Evolution" );
265  break;
266  default:
267  qFatal( "Default switch in LaralGA::gaStep" );
268  break;
269  }
270 }
271 
273  // Set evaluation done, and check which phase to go to
274  evaluationDone = true;
275  if ( generation() < numGens ) {
276  currPhase = nextGeneration_pass2;
277  } else {
278  currPhase = endEvolution;
279  }
280 }
281 
283  if ( isFinalized && !isInitialized ) return;
284 
285  isInitialized = false;
286  isFinalized = true;
287  evolutionEnd = true;
288 }
289 
290 void LaralGA::createNextGeneration() {
291  Genome& genref = *(genome());
292  muta->setGenome( genome() );
293  for( int i=0; i<nreproducing; i++ ) {
294  //--- skip for now the first nreproducing position
295  for( int j=1; j<noffspring; j++ ) {
296  //--- id of the current genome to replace
297  int id = i + j*nreproducing;
298  //--- clone and mutate the i-th selected individual
299  genref[id]->assign( genref[i] );
300  muta->mutate( genref[id] );
301  }
302  }
303  //--- also mutate the selected, excepted for the elited ones
304  int startId = 0;
305  if ( elitismEnabled ) {
306  startId = nelitism;
307  }
308  for( int i=startId; i<nreproducing; i++ ) {
309  muta->mutate( genref[i] );
310  }
311 }
312 
313 LaralGA::evaluationThread::evaluationThread( LaralGA* p, MultiTrials* eProto )
314  : parent(p), id(0), blocked(false) {
315  eval = eProto->clone();
316  eval->setGenome( p->genome() );
317  eval->setGA( p );
318 }
319 
320 LaralGA::evaluationThread::~evaluationThread() {
321  delete eval;
322  sequence.clear();
323 }
324 
325 void LaralGA::evaluationThread::runStep() {
326  if ( blocked ) {
327  return;
328  }
329 
330  parent->nextGeneration = false;
331  eval->evaluateStep();
332  if ( eval->isEvaluationDone() ) {
333  int nextIdSeq = idSeq + 1;
334  eval->finalize();
335  eval->genotype()->setRank( eval->genotype()->fitness() );
336  if ( nextIdSeq >= sequence.size() ) {
337  blocked = true;
338  return;
339  }
340  idSeq = nextIdSeq;
341  int nextId = sequence[ idSeq ];
342  id = nextId;
343  eval->initialize( parent->genome()->at( id ) );
344  }
345 }
346 
347 void LaralGA::runStepWrapper( LaralGA::evaluationThread* e ) {
348  e->runStep();
349 }
350 
351 } // end namespace farsa