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