<?xml version="1.0"?>

  Sferes2 -- Tutorial 1.0
  
    
    
      Jean-Baptiste Mouret
    
    
      Paul Tonelli
    
    
    
      2011 
    
    
      
        CeCiLL
      
    
    
      Sferes Documentation main page
    
    
  
  
    [ file:sferes2.org back to main page ]
  
  
    This short example shows how to set up a basic experiment to optimize parameters
    using a user-defined fitness.
  
  
    Running
    a sferes2 executable
    
      Waf put all the compiled files in the "build" directory. Two variants
      are produced: "default" and "debug". You should always
      run the debug version first because the default version disables assert. Each
      variant mirrors the architecture of the main sferes2 directory.
    
    
      Sferes2 contains some basic examples in the "examples" directory.
      For instance, let's check "ex_ea.cpp", a basic single-objective optimization
      of parameters. To launch the debug version, you should run:
    
    
      
build/debug/examples/ex_ea
    
    
      A verbose mode is also available: 
build/debug/examples/ex_ea -v all
    
    
      And a more verbose one: 
build/debug/examples/ex_ea -v all -v trace
    
    
      For the optimized version: 
build/default/examples/ex_ea
    
    
      Running one of those commands should create a directory named using the schema
      hostame_pid-year-month-day_hours:min:seconds. In this directory there are:
    
    
      
        a file called "bestfit.dat", which contains, for each generation,
        the value of the best fitness;
      
      
        many files calles gen_xxx where xxx is a number.
      
    
    
      These files are XML files which store the best candidates solutions for each
      generation (xxx is the generation number). For instance:
    
    
      
&lt;?xml version="1.0" encoding="UTF-8" standalone="yes" ?&gt;
&lt;!DOCTYPE boost_serialization&gt;
&lt;boost_serialization signature="serialization::archive" version="5"&gt;
  &lt;x class_id="0" tracking_level="0" version="0"&gt;
    &lt;_best class_id="1" tracking_level="0" version="1"&gt;
      &lt;px class_id="2" tracking_level="1" version="0" object_id="_0"&gt;
	&lt;_gen class_id="3" tracking_level="0" version="0"&gt;
	  &lt;_data&gt;
	    &lt;count&gt;10&lt;/count&gt;
	    &lt;item_version&gt;0&lt;/item_version&gt;
	    &lt;item&gt;0.50000423&lt;/item&gt;
	    &lt;item&gt;0.49999771&lt;/item&gt;
	    &lt;item&gt;0.49999756&lt;/item&gt;
	    &lt;item&gt;0.49999699&lt;/item&gt;
	    &lt;item&gt;0.50000197&lt;/item&gt;
	    &lt;item&gt;0.49999171&lt;/item&gt;
	    &lt;item&gt;0.5000003&lt;/item&gt;
	    &lt;item&gt;0.50000542&lt;/item&gt;
	    &lt;item&gt;0.50002229&lt;/item&gt;
	    &lt;item&gt;0.49999493&lt;/item&gt;
	  &lt;/_data&gt;
	&lt;/_gen&gt;
	&lt;_fit class_id="5" tracking_level="0" version="0"&gt;
	  &lt;_value&gt;-4.0586573e-14&lt;/_value&gt;
	  &lt;_objs&gt;
	    &lt;count&gt;0&lt;/count&gt;
	    &lt;item_version&gt;0&lt;/item_version&gt;
	  &lt;/_objs&gt;
	&lt;/_fit&gt;
      &lt;/px&gt;
    &lt;/_best&gt;
  &lt;/x&gt;
  &lt;x class_id="6" tracking_level="0" version="0"&gt;
    &lt;_mean&gt;-5376.9888&lt;/_mean&gt;
  &lt;/x&gt;
&lt;/boost_serialization&gt;

    
    
      This XML file mirrors the statistics used in this experiment (more about this
      in the next sections). At this point of this overview, you can check:
    
    
      
        the _data subtree which lists the values of the "optimal" parameters
        (the result of the experiment);
      
      
        the _fit subtree which shows the fitness value of this "optimal"
        individual;
      
      
        the _mean value which corresponds to the mean fitness in the population (another
        statistics);
      
    
  
  
    Loading a result
    file
    
      These XML files can be easily post-processed in your favorite language if needed.
      However, the same executable which generated the result files can also read
      them using the following syntax in the experience directory : 
../build/default/examples/ex_ea --load gen_1000 -o output_file -n number
    
    
      
        output_file is a text file which, most of the time (depending on the genotype
        and phenotype used), describes the best individual (or the Pareto-optimal
        set in multiobjective optimization); in this example, it contains the value
        of the parameters for the best individual:
      
    
    
      
cat output_file
8.46386e-05 -4.58956e-05 -4.88758e-05 -6.02007e-05 3.93391e-05 -0.000165701 5.96046e-06 0.00010848 0.000445843 -0.000101328
    
    
      
        number is the number of the individual described in the file you want to
        load.
      
    
  
  
    Building
    your own experiment
    
      Setting
      everything up
      
        Create
        directories and files
        
          
            At the root of the sferes repository (the main directory), use waf to
            create a new experiment. Let's call it "test":
          
        
        
          
./waf --create test
        
        
          This should have created a new directory exp/test, a waf file exp/test/wscript
          and a basic file exp/test/test.cpp. You can now edit/customize them.
        
      
      
        Compiling
        
          
            In the main sferes2 directory (not in the experiment's directory): 
./waf --exp my_exp
            If the experiment is called "test", the command line is: 
./waf --exp test
          
        
      
      
        Running
        
          
cd exp/test
../../build/debug/exp/test/test
          and for the optimized version: 
../../build/default/exp/test/test
        
      
    
    
      Customizing
      / writing the experiment
      
        Let's start with exp/testtest.cpp, /from the end of the file.
      
      
        Main
        function
        
          At the end of the file, you should see a main() function. It defines some
          types using typedef then run the evolutionary algorithm. The types describe
          our experiment (what kind of genotype? what kind of fitness? what kind
          of algorithm?). We will go back to the Params class in the following section.
          Ignore it for now.
        
        
          
int main(int argc, char **argv)
{
  // Our fitness is the class FitTest (see above), that we call
  // fit_t. Params is the set of parameters (struct Params) defined in
  // this file.
  typedef FitTest&lt;Params&gt; fit_t;
  // We define the genotype. Here we choose EvoFloat (real
  // numbers). We evolve 10 real numbers, with the params defined in
  // Params (cf the beginning of this file)
  typedef gen::EvoFloat&lt;10, Params&gt; gen_t;
  // This genotype should be simply transformed into a vector of
  // parameters (phen::Parameters). The genotype could also have been
  // transformed into a shape, a neural network... The phenotype need
  // to know which fitness to use; we pass fit_t to it.
  typedef phen::Parameters&lt;gen_t, fit_t, Params&gt; phen_t;
  // The evaluator is in charge of distributing the evaluation of the
  // population. It can be simple eval::Eval (nothing special),
  // parallel (for multicore machines, eval::Parallel) or distributed
  // (for clusters, eval::Mpi).
  typedef eval::Eval&lt;Params&gt; eval_t;
  // Statistics gather data about the evolutionary process (mean
  // fitness, Pareto front, ...). Since they can also store the best
  // individuals, they are the container of our results. We can add as
  // many statistics as required thanks to the boost::fusion::vector.
  typedef boost::fusion::vector&lt;stat::BestFit&lt;phen_t, Params&gt;, stat::MeanFit&lt;Params&gt; &gt;  stat_t;
  // Modifiers are functors which are run once all individuals have
  // been evalutated. Their typical use is to add some evolutionary
  // pressures towards diversity (e.g. fitness sharing). Here we don't
  // use this feature. As a consequence we use a "dummy" modifier that
  // does nothing.
  typedef modif::Dummy&lt;&gt; modifier_t;
  // We can finally put everything together. RankSimple is the
  // evolutionary algorithm. It is parametrized by the phenotype, the
  // evaluator, the statistics list, the modifier and the general params.
  typedef ea::RankSimple&lt;phen_t, eval_t, stat_t, modifier_t, Params&gt; ea_t;
  // We now have a special class for our experiment: ea_t. The next
  // line instantiates an object of this class
  ea_t ea;
  // We can now process the command line options and run the
  // evolutionary algorithm (if a --load argument is passed, the file
  // is loaded; otherwise, the algorithm is launched).
  run_ea(argc, argv, ea);
  //
  return 0;
}
        
      
      
        Include
        part
        
          Let's now go back to the top of the file. The file starts with the usual
          include files, which obviously depends on which classes (genotype, phenotype,
          ea, ...) you selected in the main function:
        
        
          
#include &lt;iostream&gt;
#include &lt;sferes/phen/parameters.hpp&gt;
#include &lt;sferes/gen/evo_float.hpp&gt;
#include &lt;sferes/ea/rank_simple.hpp&gt;
#include &lt;sferes/eval/eval.hpp&gt;
#include &lt;sferes/stat/best_fit.hpp&gt;
#include &lt;sferes/stat/mean_fit.hpp&gt;
#include &lt;sferes/modif/dummy.hpp&gt;
#include &lt;sferes/run.hpp&gt;
        
      
      
        Params
        
          Then, the Params structure defines the parameters of the algorithm. This
          particular way of setting them allows the compiler to propagate constants
          to the whole programm (i.e. it replaces the parameters in the code by their
          values), allowing some optimizations. This parameters will depend on the
          algorithms you chose to use in your main function.
        
        
          
struct Params
{
  struct evo_float
  {
    // we choose the polynomial mutation type
    static const mutation_t mutation_type = polynomial;
    // we choose the polynomial cross-over type
    static const cross_over_t cross_over_type = sbx;
    // the mutation rate of the real-valued vector
    static const float mutation_rate = 0.1f;
    // a parameter of the polynomial mutation
    static const float eta_m = 15.0f;
    // a parameter of the polynomial cross-over
    static const float eta_c = 10.0f;
  };
  struct pop
  {
    // size of the population
    static const unsigned size = 200;
    // number of generations
    static const unsigned nb_gen = 2000;
    // how often should the result file be written (here, each 5
    // generation)
    static const int dump_period = 5;
    // how many individuals should be created during the random
    // generation process?
    static const int initial_aleat = 1;
    // used by RankSimple to select the pressure
    static const float coeff = 1.1f;
    // the number of individuals which are kept from one generation to
    // another (elitism)
    static const float keep_rate = 0.6f;    
  };
  struct parameters
  {
    // maximum value of the phenotype parameters
    static const float min = -10.0f;
    // minimum value
    static const float max = 10.0f;
  };
};
        
      
      
        Fitness
        function
        
          Last, it's time to write the fitness function. It's a special class with
          an "eval()" function which derives from fit::Fitness. It has
          to fill this-&gt;_value in single-objective optimization and this-&gt;_objs
          in multiobjective optimization. In this example, we want to maximize -\sum_i
          p_i^4, where p is the individual's phenotype.
        
        
          
SFERES_FITNESS(FitTest, sferes::fit::Fitness)
{
 public:
  // indiv will have the type defined in the main (phen_t)
  template&lt;typename Indiv&gt;
    void eval(const Indiv&amp; ind) 
  {
    float v = 0;
    for (unsigned i = 0; i &lt; ind.size(); ++i)
      {
	float p = ind.data(i);
	v += p * p * p * p;
      }
    this-&gt;_value = -v;
  }
};
        
      
    
    
      [[[file:sferes2.org][{back to main page}]]]
    
  

