Documentation:Tutorials:Coevolution
From Beagle
This section is a tutorial on Open BEAGLE's co-evolution framework. It first presents quickly the characteristics of the co-evolutionary framework before going into a specific case, that is the co-evolutionary symbolic regression, which is available as an Open BEAGLE example.
Contents |
Open BEAGLE Co-evolution framework
The co-evolution facilities of Open BEAGLE are based on the use of multi-threaded programming. Each of the co-evolving species run in its own thread, sharing individuals only for the fitness evaluation. The co-evolution framework provides a special fitness evaluation operator from which all the different species fitness evaluation operators should be derived. The fitness evaluation process is slightly complicated by these, as each species fitness evaluation operator should be programmed in order to make different evaluation sets with the individuals to evaluate, according to the mating strategy used in the co-evolutionary application. Other than that, the different species are programmed very similarly to a standard Open BEAGLE application.
Co-evolutionary Symbolic Regression Example
The actual co-evolution tutorial is based on the co-evolutionary symbolic regression example of Open BEAGLE, available in directory examples/Coev/coev_symbreg of the source code. It consists in doing a classical symbolic regression of an arithmetic equation with GP, but with a competitive co-evolution of test cases. So the first species is a population of GP trees that are evolving in order to minimize the RMS error over a given number of test cases (samples) of a mathematical equation. The second species is made of individuals representing the x-values of the samples themselves evolved with a real-valued GA. The fitness function of this second species consist in increasing the RMS error of the GP symbolic expressions. Each individual of the test cases selection species represents a fixed number of x-values (precisely 20 test cases) taken in the [-1,1] range, while the corresponding y-values are computed using the target symbolic expression. The pairing betweem the two species is done by evaluating one species individual at a given generation using the best solution of the other species of previous generation, and vice-versa. For the first generation, the individual of the other species is selected randomly.
This co-evolutionary symbolic regression application has been previously presented in Methods for Evolving Robust Programs of Panait and Luke (GECCO 2003).
Main Routine
The main routine of the co-evolutionary symbolic regression example is provided in file CoevSymbregMain.cpp.
using namespace std;
using namespace Beagle;
int main(int argc, char** argv) {
try {
// Deduce configuration filename from argv[0].
std::string lConfigFilename1 = "symgp-thread.conf";
std::string lConfigFilename2 = "trainset-thread.conf";
std::string lProgramName = argv[0];
std::string::size_type lLibsPos = lProgramName.find(std::string(".libs"));
std::string::size_type lSlashPos = lProgramName.find_last_of(std::string("/\\"), lLibsPos);
if(lSlashPos != std::string::npos) {
lConfigFilename1.insert(0, lProgramName, 0, lSlashPos+1);
lConfigFilename2.insert(0, lProgramName, 0, lSlashPos+1);
}
// Creates and starts first thread/population for co-evolution.
SymGPThread lThread1(lConfigFilename1);
lThread1.run();
// Creates and starts second thread/population for co-evolution.
TrainSetThread lThread2(lConfigFilename2);
lThread2.run();
// Wait for the threads to finish.
lThread1.wait();
lThread2.wait();
}
catch(std::exception& inException) {
std::cerr << "Standard exception catched:" << std::endl << std::flush;
std::cerr << inException.what() << std::endl << std::flush;
return 1;
}
}
This main routine consists mainly in building the two species threads and launching them. The first thread is for the evolution of GP arithmetic expressions, while the second thread is for the test cases selection species. The multi-threading implementation is based on the PACC threading facilities. Each thread/species receives a configuration filename, into which is specified the configuration of the species. The method PACC::Thread::run, called for both the lThread1 and lThread2 objects, starts the child threads in parallel to the actual main thread, while the PACC::Thread::wait is called for both these thread objects in order to block the main thread until the two child threads have finished, that is when the two species co-evolution complete.
GP Trees Thread Class
The evolution of the symbolic expression trees is done by running the lThread1 object in the main routine. This object is of type SymGPThread, implemented in files SymGPThread.hpp and SymGPThread.cpp.
class SymGPThread : public PACC::Threading::Thread {
public:
SymGPThread(std::string inConfigFilename="symgp-thread.conf") :
mConfigFilename(inConfigFilename)
{ }
~SymGPThread()
{
wait();
}
protected:
virtual void main()
{
try {
// 1: Build primitives.
GP::PrimitiveSet::Handle lSet = new GP::PrimitiveSet;
lSet->insert(new GP::Add);
lSet->insert(new GP::Subtract);
lSet->insert(new GP::Multiply);
lSet->insert(new GP::Divide);
lSet->insert(new GP::TokenT<Double>("X"));
lSet->insert(new GP::EphemeralDouble);
// 2: Build a system.
GP::System::Handle lSystem = new GP::System(lSet);
// 3: Build evaluation operator.
SymGPEvalOp::Handle lEvalOp = new SymGPEvalOp;
// 4: Build an evolver and a vivarium.
GP::Evolver::Handle lEvolver = new GP::Evolver(lEvalOp);
lEvolver->addOperator(new Coev::TermBroadcastOp);
GP::Vivarium::Handle lVivarium =
new GP::Vivarium(new GP::Tree::Alloc, new FitnessSimpleMin::Alloc);
// 5: Initialize and evolve the vivarium.
lEvolver->initialize(lSystem, mConfigFilename);
lEvolver->evolve(lVivarium);
}
catch(Exception& inException) {
inException.terminate(std::cerr);
}
catch(std::exception& inException) {
std::cerr << "Standard exception catched:" << std::endl << std::flush;
std::cerr << inException.what() << std::endl << std::flush;
}
}
std::string mConfigFilename; //!< Configuration filename
};
This PACC::Thread derived object configure Open BEAGLE for a symbolic regression with GP, pretty much like it is done with the classical symbolic regression example. The difference is that the declaration is not in the usual C main routine, but rather in the virtual SymGPThread::main method, which do not provide access to the command-line. Given that, the species is configured from a configuration file stored in member mConfigFilename. The fitness evaluation operator is also different from the original symbolic regression fitness evaluation operator and will be explained in more details in a following section.
Test Cases Selection Thread Class
Comparatively to the evolution of GP trees, the evolution of test cases selection is done in and independant thread, launched by running the lThread2 object in the main routine. This thread is defined in class TrainSetThread in files TrainSetThread.hpp and TrainSetThread.cpp.
class TrainSetThread : public PACC::Threading::Thread {
public:
TrainSetThread(std::string inConfigFilename="trainset-thread.conf") :
mConfigFilename(inConfigFilename)
{ }
~TrainSetThread()
{
wait();
}
protected:
virtual void main()
{
try {
// 1. Build the system.
System::Handle lSystem = new System;
// 2. Build evaluation operator.
TrainSetEvalOp::Handle lEvalOp = new TrainSetEvalOp;
// 3. Instanciate the evolver and the vivarium for float vectors GA population.
GA::FloatVector::Alloc::Handle lFVAlloc = new GA::FloatVector::Alloc;
Vivarium::Handle lVivarium = new Vivarium(lFVAlloc);
// 4. Set representation, individuals of 1 float vector, each with 20 float values.
const unsigned int lVectorSize=20;
// 5. Initialize the evolver and evolve the vivarium.
GA::EvolverFloatVector::Handle lEvolver = new GA::EvolverFloatVector(lEvalOp, lVectorSize);
lEvolver->addOperator(new Coev::TermBroadcastOp);
lEvolver->initialize(lSystem, mConfigFilename);
lEvolver->evolve(lVivarium);
}
catch(Exception& inException) {
inException.terminate(std::cerr);
}
catch(std::exception& inException) {
std::cerr << "Standard exception catched:" << std::endl << std::flush;
std::cerr << inException.what() << std::endl << std::flush;
}
}
std::string mConfigFilename; //!< Configuration filename
};
Again, this class implement a real-valued GA application pretty much the same way it would be done for a mono-processor application. The population is made is real-valued vectors of 20 values each, representing the x-values of the test cases to use. The configuration of this species is given through the configuration file named by the mConfigFilename variable.
General Fitness Evaluation Class
There are elements of the fitness evaluation that is shared between the two evolving species. Relating to Open BEAGLE, this is translated by some functions that are shared between the two fitness evaluations operators of each species. In order to share these, a general fitness evaluation class has been defined. The specific fitness evaluation operators are then derived from this general fitness evaluation operator, thus inheriting the common methods. For the actual case, the class CoSymEvalOp (implemented in files CoSymEvalOp.hpp and CoSymEvalOp.cpp) has been defined.
class CoSymEvalOp : public Beagle::Coev::GPEvaluationOp {
public:
explicit CoSymEvalOp(std::string inName="CoSymEvalOp") :
Coev::GPEvaluationOp(4, inName)
{ }
In this constructor, the name of the fitness evaluation operator is received as argument inName. This name will be changed in the derived fitness evaluation operators. The constructor of the parent Coev::GPEvaluationOp class is call with as first argument the trigger value, and as second argument the name received of the fitness evaluation operator. The trigger is value is very important, as it states how many individual sets should be received before starting a co-evolutionary fitness evaluation. In the actual case, this trigger value is 4 as four different evaluation sets are necessary for fitness evaluation: 1) the actual generation population of GP tree expressions, 2) the actual generation population of test cases, 3) the previous generation best-of-population GP arithmetic expression (lowest RMS error), and 4) the previous generation best-of-population test cases selection (hardest test cases).
virtual void makeSets(Beagle::Individual::Bag& ioIndivBag, Beagle::Context::Handle ioContext) =0;
The pure virtual makeSets method is defined in the specialized fitness evaluation operators to generate the sets of individuals used for fitness evaluation. Each species provides their respective individuals sets to the co-evolutionary fitness evaluation facilities through this method.
The following methods is central to the co-evolutionary application. It implements the fitness evaluation for all the species by receiving the evaluation sets. This method is called internally when the number of evaluation sets provided reach the trigger value.
virtual void evaluateSets(Beagle::Coev::GPEvaluationOp::EvalSetVector& ioSets)
{
Beagle_AssertM(ioSets.size() == 4);
// mID: GPSet(0), lBestGPSet(1), TrainSet(2), BestTrainSet(3)
unsigned int lGPSet=0; // Index for GP set in ioSets
unsigned int lTrainSet=0; // Index for training set in ioSets
unsigned int lBestTrainSet=0; // Index for best training set in ioSets
unsigned int lBestGPSet=0; // Index for best GP set in ioSets
switch(ioSets[0].mID) {
case 0: lGPSet = 0; break;
case 1: lBestGPSet = 0; break;
case 2: lTrainSet = 0; break;
case 3: lBestTrainSet = 0; break;
default: throw Beagle_RunTimeExceptionM("Undefined eval set ID!");
}
switch(ioSets[1].mID) {
case 0: lGPSet = 1; break;
case 1: lBestGPSet = 1; break;
case 2: lTrainSet = 1; break;
case 3: lBestTrainSet = 1; break;
default: throw Beagle_RunTimeExceptionM("Undefined eval set ID!");
}
switch(ioSets[2].mID) {
case 0: lGPSet = 2; break;
case 1: lBestGPSet = 2; break;
case 2: lTrainSet = 2; break;
case 3: lBestTrainSet = 2; break;
default: throw Beagle_RunTimeExceptionM("Undefined eval set ID!");
}
switch(ioSets[3].mID) {
case 0: lGPSet = 3; break;
case 1: lBestGPSet = 3; break;
case 2: lTrainSet = 3; break;
case 3: lBestTrainSet = 3; break;
default: throw Beagle_RunTimeExceptionM("Undefined eval set ID!");
}
Beagle_AssertM(ioSets[lGPSet].mIndividuals.size() > 0);
Beagle_AssertM(ioSets[lTrainSet].mIndividuals.size() > 0);
Beagle_AssertM(ioSets[lBestTrainSet].mIndividuals.size() == 1);
Beagle_AssertM(ioSets[lBestGPSet].mIndividuals.size() == 1);
The previous code is used to order the individuals sets given their IDs. Indeed, the evaluation set are provided unordered to the co-evolution facilities, so the application should order them according to the application-specific ID scheme used. The IDs of the evaluation sets are unused by the co-evolutionary facilities, they can be of any values, so the application programmer is left to implement any identification scheme pertinent for her application.
// Match GP equations set with best training set
Individual& lBestTrainIndiv = *ioSets[lBestTrainSet].mIndividuals[0];
Beagle_AssertM(lBestTrainIndiv.size() == 1);
GA::FloatVector::Handle lFVBestTrain = castHandleT<GA::FloatVector>(lBestTrainIndiv[0]);
Beagle_AssertM(lFVBestTrain->size() == 20);
GP::Context& lGPContext = castObjectT<GP::Context&>(*ioSets[lGPSet].mContext);
unsigned int lGPSetSize = ioSets[lGPSet].mIndividuals.size();
for(unsigned int i=0; i<lGPSetSize; ++i) {
GP::Individual& lGPIndividual =
castObjectT<GP::Individual&>(*ioSets[lGPSet].mIndividuals[i]);
double lRMS = evaluateRMS(*lFVBestTrain, lGPIndividual, lGPContext);
assignFitness(new FitnessSimpleMin(lRMS), lGPIndividual, lGPContext);
}
The first operation done is to evaluate the fitness of the GP equations. This is done through the CoSymEvalOp::evaluateRMS method, which is local and totally specific to the co-evolutionary symbolic regression example. For each individual composing the evaluation set associated to the GP trees of the actual generation, the RMS is computed using hardest test cases of previous generation. The fitness is assigned to each of these individuals throught the method Beagle::Coev::EvaluationOp::assignFitness.
// Match training set with best symbolic regression function
GP::Individual& lBestGPIndiv =
castObjectT<GP::Individual&>(*ioSets[lBestGPSet].mIndividuals[0]);
GP::Context& lGPContext2 = castObjectT<GP::Context&>(*ioSets[lBestGPSet].mContext);
unsigned int lTrainSetSize = ioSets[lTrainSet].mIndividuals.size();
for(unsigned int i=0; i<lTrainSetSize; ++i) {
Individual& lTrainIndiv = *ioSets[lTrainSet].mIndividuals[i];
Beagle_AssertM(lTrainIndiv.size() == 1);
GA::FloatVector::Handle lFVTrain = castHandleT<GA::FloatVector>(lTrainIndiv[0]);
Beagle_AssertM(lFVTrain->size() == 20);
double lRMS = evaluateRMS(*lFVTrain, lBestGPIndiv, lGPContext2);
assignFitness(new FitnessSimple(lRMS), lTrainIndiv, *ioSets[lTrainSet].mContext);
}
}
In a second time, the fitness of the test cases is evaluated against the best GP expressions of previous generation. As with the GP expressions, the RMS error is evaluated using the CoSymEvalOp::evaluateRMS method, specific to this example, while the fitness value is assigned using the Beagle::Coev::EvaluationOp::assignFitness method. Note that fitness measure used is of type Beagle::FitnessSimple this time, as test cases are evolved in order to maximise the RMS error of the arithmetic expressions.
protected:
double evaluateRMS(Beagle::GA::FloatVector& inTrainSet,
Beagle::GP::Individual& inExpression,
Beagle::GP::Context& ioContext) const
{
inExpression[0]->setContextToNode(0, ioContext);
GP::Individual::Handle lOldIndivHandle = ioContext.getIndividualHandle();
ioContext.setIndividualHandle(&inExpression);
double lSquareError = 0.0;
for(unsigned int i=0; i<inTrainSet.size(); ++i) {
Double lX = inTrainSet[i];
if(lX > 1.0) lX = 1.0;
if(lX < -1.0) lX = -1.0;
Double lY = (lX*(lX*(lX*(lX+1.0)+1.0)+1.0));
setValue("X", lX, ioContext);
Double lResult;
inExpression.run(lResult, ioContext);
double lError = std::fabs(lY - lResult);
lSquareError += (lError*lError);
}
ioContext.setIndividualHandle(lOldIndivHandle);
double lMSE = lSquareError / inTrainSet.size();
return std::sqrt(lMSE);
}
};
The CoSymEvalOp::evaluateRMS method is used internally to the fitness evaluation operator to compute the RMS error of GP arithmetic expression inExpression using the test cases given in inTrainSet. The code looks like pretty much like the SymbRegEvalOp::evaluate method of the fitness evaluation operator of the classical symbolic regression example, as the operation done is obviously very similar.
GP Trees Fitness Evaluation Class
The fitness evaluation operator specific to the evaluation of the GP trees has been defined as class SymGPEvalOp (implemented in files SymGPEvalOp.hpp and SymGPEvalOp.cpp). As explained before, this operator inherits from class CoSymEvalOp, so all the general fitness evaluation method of the co-evolutionary symbolic regression example.
class SymGPEvalOp : public CoSymEvalOp {
public:
explicit SymGPEvalOp() :
CoSymEvalOp("SymGPEvalOp")
{ }
virtual void makeSets(Beagle::Individual::Bag& ioIndivBag,
Beagle::Context::Handle ioContext)
{
// If last generation best individual is NULL handle, choose a random individual.
if(mLastGenBestIndividual == NULL) {
unsigned int lRandomIndex =
ioContext->getSystem().getRandomizer().rollInteger(0, ioContext->getDeme().size()-1);
GP::Individual::Alloc::Handle lIndivAlloc =
castHandleT<GP::Individual::Alloc>(ioContext->getDeme().getTypeAlloc());
GP::Individual::Handle lIndivToCopy =
castHandleT<GP::Individual>(ioContext->getDeme()[lRandomIndex]);
mLastGenBestIndividual = castHandleT<GP::Individual>(lIndivAlloc->cloneData(*lIndivToCopy));
}
// Eval set for GP Symbolic equation
EvalSet lSymGPEvalSet(ioIndivBag, ioContext, 0);
addSet(lSymGPEvalSet, false);
// Eval set of best GP individual
GP::Individual::Bag lBestSymGPIndiv;
lBestSymGPIndiv.push_back(mLastGenBestIndividual);
EvalSet lBestSymGPEvalSet(lBestSymGPIndiv, ioContext, 1);
addSet(lBestSymGPEvalSet, true);
// Get a copy of the best individual for next generation
unsigned int lBestIndivIndex = 0;
float lBestIndivIndexFits =
castHandleT<FitnessSimple>(ioContext->getDeme()[0]->getFitness())->getValue();
for(unsigned int i=1; i<ioContext->getDeme().size(); ++i) {
float lFitness =
castHandleT<FitnessSimple>(ioContext->getDeme()[i]->getFitness())->getValue();
if(lFitness < lBestIndivIndexFits) {
lBestIndivIndexFits = lFitness;
lBestIndivIndex = i;
}
}
GP::Individual::Handle lIndivToCopy =
castHandleT<GP::Individual>(ioContext->getDeme()[lBestIndivIndex]);
GP::Individual::Alloc::Handle lIndivAlloc =
castHandleT<GP::Individual::Alloc>(ioContext->getDeme().getTypeAlloc());
mLastGenBestIndividual = castHandleT<GP::Individual>(lIndivAlloc->cloneData(*lIndivToCopy));
}
protected:
Beagle::GP::Individual::Handle mLastGenBestIndividual; //!< Copy of last gen. best individual.
};
Only the makeSets method is overdefined for this operator, as all other fitness evaluation operations are done in the general fitness evaluation operator CoSymEvalOp. This method generates two evaluation sets, that is the whole population of GP arithmetic expression for the actual generation (evaluation set ID: 0) and the best-of-population GP arithmetic expression of previous generation (evaluation set ID: 1). The evaluation sets are given to the co-evolutionary facilities through calls to the addSet, defined in the root co-evolutionary fitness evaluation operator Beagle::Coev::EvaluationOp. This method addSet takes two arguments: the evaluation set to add and a blocking flag. The blocking flag is a boolean argument used to block the fitness evaluation for the actual species until the co-evolutionary fitness evaluation is done for the all evaluation sets provided. It is usually set to true when the last evaluation set related to the actual species is added to the co-evolutionary fitness evaluation system. In the actual case, the GP arithmetic expression species should add two evaluation sets, so the blocking flag is set to true only for the addition of the second evaluation set. The addSet returns only when the evaluateSets method returns, which itself have been called when the number of evaluation sets added reach the trigger value. In the actual case, the evaluateSets method is defined in the parent CoSymEvalOp class. Thus, it can be taken for granted that the fitness of the actual generation GP arithmetic expressions is correctly evaluated when the call to addSet returns. The remaining of the makeSets method is to copy this generation best-of-population individual for using it in next generation.
Test Cases Fitness Evaluation Class
Symetrically to the GP trees fitness evaluation operator, the test cases selection fitness evaluation operator TrainSetEvalOp (implemented in files TrainSetEvalOp.hpp and TrainSetEvalOp.cpp) inherits from the general CoSymEvalOp fitness evaluation class. As with SymGPEvalOp, it overdefines the makeSets, used to prepare the individual to evaluation by co-evolutionary mating, in the actual case by making a set with all the individuals of the population and another with the best individual of previous generation.
class TrainSetEvalOp : public CoSymEvalOp {
public:
explicit TrainSetEvalOp() :
CoSymEvalOp("TrainSetEvalOp")
{ }
virtual void makeSets(Beagle::Individual::Bag& ioIndivBag,
Beagle::Context::Handle ioContext)
{
// If last generation best individual is NULL handle, choose a random individual.
if(mLastGenBestIndividual == NULL) {
unsigned int lRandomIndex =
ioContext->getSystem().getRandomizer().rollInteger(0, ioContext->getDeme().size()-1);
Individual::Handle lIndivToCopy = ioContext->getDeme()[lRandomIndex];
Individual::Alloc::Handle lIndivAlloc = ioContext->getDeme().getTypeAlloc();
mLastGenBestIndividual =
castHandleT<Individual>(lIndivAlloc->cloneData(*lIndivToCopy));
}
// Eval set for best training set
Individual::Bag lBestTrainSetIndiv;
lBestTrainSetIndiv.push_back(mLastGenBestIndividual);
EvalSet lBestTrainEvalSet(lBestTrainSetIndiv, ioContext, 3);
addSet(lBestTrainEvalSet, false);
// Eval set for training sets
EvalSet lTrainEvalSet(ioIndivBag, ioContext, 2);
addSet(lTrainEvalSet, true);
// Get a copy of the best individual for next generation
unsigned int lBestIndivIndex = 0;
float lBestIndivIndexFits =
castHandleT<FitnessSimple>(ioContext->getDeme()[0]->getFitness())->getValue();
for(unsigned int i=1; i<ioContext->getDeme().size(); ++i) {
float lFitness =
castHandleT<FitnessSimple>(ioContext->getDeme()[i]->getFitness())->getValue();
if(lFitness > lBestIndivIndexFits) {
lBestIndivIndexFits = lFitness;
lBestIndivIndex = i;
}
}
Individual::Handle lIndivToCopy = ioContext->getDeme()[lBestIndivIndex];
Individual::Alloc::Handle lIndivAlloc = ioContext->getDeme().getTypeAlloc();
mLastGenBestIndividual =
castHandleT<Individual>(lIndivAlloc->cloneData(*lIndivToCopy));
}
protected:
Beagle::Individual::Handle mLastGenBestIndividual; //!< Copy of last gen. best individual.
};
This method is very similar to the previous one. It starts by selecting randomly an individual if the evolution is at its first generation as the individual to use to pair with the other species individual. Then, two evaluation sets are added to the co-evolutionary fitness evaluation system, that is this generation population of test cases individuals (evaluation set ID: 2) and the best-of-population individual of last generation (evaluation set ID: 3) - the randomly selection test cases individual if at the first generation of the evolution. The block flag is set to true for the second call of Beagle::Coev::EvaluationOp::addSet, as the necessary two evaluation sets have been added. Once the fitness evaluation complete, with the second addSeet call returning, the best-of-population individual of this generation is cloned in order to be used as the fourth evaluation set of next generation.
Configuration Files
In order to correctly tune the parameters of the system, two configuration files are necessary, one for each co-evolving species. The first one, given in file symgp-thread.conf, is used to configure the GP trees evolution species.
<?xml version="1.0" encoding="ISO-8859-1"?>
<Beagle>
<Evolver>
<BootStrapSet>
<IfThenElseOp parameter="ms.restart.file" value="">
<PositiveOpSet>
<GP-InitHalfOp/>
<SymGPEvalOp/>
<GP-StatsCalcFitnessSimpleOp/>
</PositiveOpSet>
<NegativeOpSet>
<MilestoneReadOp/>
</NegativeOpSet>
</IfThenElseOp>
<TermMaxGenOp/>
<Coev-TermBroadcastOp/>
<MilestoneWriteOp/>
</BootStrapSet>
<MainLoopSet>
<SelectTournamentOp/>
<GP-CrossoverOp/>
<GP-MutationStandardOp/>
<GP-MutationShrinkOp/>
<GP-MutationSwapOp/>
<GP-MutationSwapSubtreeOp/>
<SymGPEvalOp/>
<GP-StatsCalcFitnessSimpleOp/>
<TermMaxGenOp/>
<Coev-TermBroadcastOp/>
<MilestoneWriteOp/>
</MainLoopSet>
</Evolver>
<Register>
<Entry key="ec.pop.size">500</Entry>
<Entry key="lg.console.level">2</Entry>
<Entry key="lg.file.name">symgp-thread.log</Entry>
<Entry key="ms.write.prefix">symgp-thread</Entry>
</Register>
</Beagle>
The second configuration file, trainset-thread.conf, is used this time to configuration the test cases selection species.
<?xml version="1.0" encoding="ISO-8859-1"?>
<Beagle>
<Evolver>
<BootStrapSet>
<IfThenElseOp parameter="ms.restart.file" value="">
<PositiveOpSet>
<GA-InitFltVecOp/>
<TrainSetEvalOp/>
<StatsCalcFitnessSimpleOp/>
</PositiveOpSet>
<NegativeOpSet>
<MilestoneReadOp/>
</NegativeOpSet>
</IfThenElseOp>
<TermMaxGenOp/>
<Coev-TermBroadcastOp/>
<MilestoneWriteOp/>
</BootStrapSet>
<MainLoopSet>
<SelectTournamentOp/>
<GA-CrossoverUniformFltVecOp/>
<GA-MutationGaussianFltVecOp/>
<TrainSetEvalOp/>
<StatsCalcFitnessSimpleOp/>
<TermMaxGenOp/>
<Coev-TermBroadcastOp/>
<MilestoneWriteOp/>
</MainLoopSet>
</Evolver>
<Register>
<Entry key="ec.pop.size">128</Entry>
<Entry key="ga.init.maxvalue">1</Entry>
<Entry key="ga.init.minvalue">-1</Entry>
<Entry key="ga.float.maxvalue">1</Entry>
<Entry key="ga.float.minvalue">-1</Entry>
<Entry key="lg.console.level">0</Entry>
<Entry key="lg.file.name">trainset-thread.log</Entry>
<Entry key="ms.write.prefix">trainset-thread</Entry>
</Register>
</Beagle>
These configuration files are identical to those usually used in application with a single species. The parameters of each species can be changed in their respective files as usual. A particular point to mention is that the console log level used for the test cases species is set to 0, in order to deactivate it. It is necessary as only one species can be logged to the standard output, otherwize the output will be a random mess of logging output from both species. The logging for the test cases species is still done in file trainset-thread.log given to parameter lg.file.name.
Another point specific to the co-evolution framework in these configuration file is the used of the Coev-TermBroadcastOp termination operator. This operator is used to synchronize the evolution threads and to terminate the evolution loop for each species once one of the species reached a termination condition. The attentive reader might have noticed that this operator have been added to the evolver in the main method of the thread of each species.
