Martin1887 / oxigen

Fast, parallel, extensible and adaptable genetic algorithms framework written in Rust
Mozilla Public License 2.0
167 stars 20 forks source link

Extra example(s) #4

Closed davidbe closed 5 years ago

davidbe commented 5 years ago

Would it be possible to provide more examples?

I'd like to see how a more simple problem is tackled with oxigen, so to see how the mechanism works. Could this example (https://deap.readthedocs.io/en/master/examples/ga_onemax.html) be translated to oxigen?

I really would like to help, but my Rust-knowledge is not good enough...

Thanks in advance!

Martin1887 commented 5 years ago

Hello.

I have just provided a OneMax example. The explanation below:

  1. Firstly, you have to create a struct representing your genotype. This structmust to be able to be cloned and printed (Clone and Display traits). For this problem, our struct is a vector of booleans:
    
    #[derive(Clone)]
    struct OneMax(Vec<bool>);

impl Display for OneMax { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "{:?}", self.0) } }


2. In addition, the struct must to be able to be created from an iterator, simply collecting it in this case:

impl FromIterator for OneMax { fn from_iter(iter: I) -> Self where I: IntoIterator, { OneMax { 0: iter.into_iter().collect(), } } }


3. Now the `Genotype` functions must to be implemented. Some of them have a default implementation, but the following ones must to be implemented always:
 - `iter` and `into_iter`, needed to iterate through the genes. These functions are always equal for a vector like this:

fn iter(&self) -> std::slice::Iter { self.0.iter() } fn into_iter(self) -> std::vec::IntoIter { self.0.into_iter() }


 - `generate`, a manner of creating one individual. In this case random booleans:

fn generate(size: &Self::ProblemSize) -> Self { let mut individual = Vec::with_capacity(size as usize); let mut rgen = SmallRng::from_entropy(); for _i in 0..size { individual.push(rgen.sample(Standard)); } OneMax(individual) }


 - `fitness`, the fitness function of each individual. In this case, the number of elements in the vector that are `true`:

fn fitness(&self) -> f64 { (self.0.iter().filter(|el| **el).count()) as f64 }


 - `mutate`, the mutation function of a gene. In this case, flipping the boolean:

fn mutate(&mut self, _rgen: &mut SmallRng, index: usize) { self.0[index] = !self.0[index]; }


 - `is_solution`, a function that says if an individual is a solution of the problem. In this case, the fitness must be equal to the number of genes:

fn is_solution(&self, fitness: f64) -> bool { fitness as usize == self.0.len() }


4. Lastly, a `GeneticExecution` is created with the parameters of the genetic algorithm. The builder does not require any function because there is a default for all ones, but at least `population_size` and `genotype_size` should be provided to indicate the size of your problem. `mutation_rate`, `selection_rate` and `select_function` here are optional, but they increase the performance of the genetic algorithm because constant mutation and selection rates are not a good idea (exploration in the first steps and explode after them is good). `Cup` is the default selection function, but indicating it is more explicit:

let (solutions, generation, _progress, _population) = GeneticExecution::<bool, OneMax>::new() .population_size(population_size) .genotype_size(problem_size) .mutation_rate(Box::new(MutationRates::Linear(SlopeParams { start: f64::from(problem_size as u32) / (8_f64 + 2_f64 * log2) / 100_f64, bound: 0.005, coefficient: -0.0002, }))).selection_rate(Box::new(SelectionRates::Linear(SlopeParams { start: log2 - 2_f64, bound: log2 / 1.5, coefficient: -0.0005, }))).select_function(Box::new(SelectionFunctions::Cup)) .run();



If you have any questions do not hesitate in reopen the issue.
Martin1887 commented 5 years ago

In 2.0 version this change a bit. The following FromIterator implementation:

impl FromIterator<bool> for OneMax {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = bool>,
    {
        OneMax {
            0: iter.into_iter().collect(),
        }
    }
}

is replaced by the from_iter function of Genotype:

fn from_iter<I: Iterator<Item = bool>>(&mut self, genes: I) {
    self.0 = genes.collect();
}