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