1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

extern crate rayon;
extern crate rand;

use std::sync::{Arc, RwLock};
use rand::Rng;
use rand::rngs::ThreadRng;
use rand::seq::SliceRandom;
use rayon::prelude::*;
use super::generation::{Container, Family, Member};
use super::genome::Genome;



//////////////////////////////////////////////////////////////////////////////////////////
/// Note these should not be directly exposed to the user as to avoid confusion with   ///
/// too many knobs to turn to create a population. Instead, provide functions to add   ///
/// them and defaults if they are not added. These are not nessesarily needed options, ///
/// they are add-ons and really only for if you really want to test around with your   ///
/// structure that is evolving, provides users with more options wich is always good   ///
//////////////////////////////////////////////////////////////////////////////////////////



/// Implement a way to pick which way to pick those members who 
/// survice each generation, in other words - pick who gets to stay, 
/// those who do not get to stay 'die off' and are replaced by the children
/// 
/// Fittest - the default option, the top member from each species
/// TopNumber - given a number, keep the top number regardless of species
/// TopPercent - given a percent out of 100, keep the top percent regardless of species
#[derive(Debug, Clone)]
pub enum SurvivalCriteria {
    Fittest,
    TopNumber(usize),
    TopPercent(f32)
}


/// Implement a way to pick parents of children, in other words
/// how is the rest of the population generation after those who 
/// don't survice die out.
/// 
/// BiasedRandom - the default option, statistically pick more fit parents
///                however allow for less fit parents to be picked as well. This is 
///                kinda like putting the members in a species on a curve and randomly 
///                picking from that distribution 
/// OnlySurvivers - those who survive are only allowed to reproduce
/// BestInEachSpecies - only the best in each species are allowed to reproduce
/// MostDifferent - Pick one parent, then find the parent most different from it (structurally) 
///                 and use that as the other parent. Note this could lead to large expansion in population
#[derive(Debug, Clone)]
pub enum ParentalCriteria {
    BiasedRandom,
    BestInSpecies,
}



/// Implement the survival enum
impl SurvivalCriteria {


    /// Based on the survival critera, given a vec of containers and families, pick who survives
    #[inline]
    pub fn pick_survivers<T, E>(&self, members: &mut Vec<Container<T, E>>, families: &Vec<Family<T, E>>) -> Option<Vec<Arc<RwLock<T>>>>
        where
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync
    {
        match self {
            Self::Fittest => {
                Some(families.par_iter()
                    .map(|x| x.read().unwrap().fittest().1)
                    .collect::<Vec<_>>())
            },
            Self::TopNumber(num) => {
                SurvivalCriteria::get_top_num(*num, members)
            },
            Self::TopPercent(perc) => {
                let num_to_survive = (members.len() as f32 * perc) as usize;
                SurvivalCriteria::get_top_num(num_to_survive, members)
            }
        }
    }



    /// TopNumer and TopPercent are basically the same so this function does the job of both of them,
    /// just convert the percent to a number before calling the function
    #[inline]
    fn get_top_num<T, E>(num_to_keep: usize, members: &mut Vec<Container<T, E>>) -> Option<Vec<Arc<RwLock<T>>>>
        where
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync
    {
        members.as_mut_slice()
            .par_sort_by(|a, b| {
                b.fitness_score.partial_cmp(&a.fitness_score).unwrap()
            });
        Some((0..num_to_keep)
            .into_iter()
            .map(|i| Arc::clone(&members[i].member))
            .collect())
    }

}




/// implement the pick parents
impl ParentalCriteria {


    /// Find two parents to crossover and produce a child
    #[inline]
    pub fn pick_parents<T, E>(&self, inbreed_rate: f32, families: &Vec<Family<T, E>>) -> Option<((f32, Member<T>), (f32, Member<T>))> 
        where
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync 
    {
        match self {
            Self::BiasedRandom => {
                return Some(self.create_match(inbreed_rate, families))
            },
            Self::BestInSpecies => {
                let mut r = rand::thread_rng();
                let child_one = families.choose(&mut r)?.read().unwrap().fittest();
                let child_two = families.choose(&mut r)?.read().unwrap().fittest();
                return Some((child_one, child_two))
            }
        }
    }



    /// pick two parents to breed a child - these use biased random ways of picking 
    /// parents and returns a tuple of tuples where the f32 is the parent's fitness,
    /// and the type is the parent itself
    #[inline]
    fn create_match<T, E>(&self, inbreed_rate: f32, families: &Vec<Family<T, E>>) -> ((f32, Member<T>), (f32, Member<T>)) 
        where
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync
    {
        let mut r = rand::thread_rng();
        let (species_one, species_two);
        // get two species to pick from taking into account an inbreeding rate - an inbreed can happen without this 
        if r.gen::<f32>() < inbreed_rate {
            let temp = self.get_biased_random_species(&mut r, families).unwrap();
            species_one = Arc::clone(&temp);
            species_two = temp;
        } else {
            species_one = self.get_biased_random_species(&mut r, families).unwrap();
            species_two = self.get_biased_random_species(&mut r, families).unwrap();
        }
        // get two parents from the species, again the parent may be the same 
        let parent_one = self.get_biased_random_member(&mut r, &species_one);
        let parent_two = self.get_biased_random_member(&mut r, &species_two);
        // return the parent tuples
        (parent_one, parent_two)
    }



    /// get a biased random species from the population to get members from
    /// this gets a random species by getting the total adjusted fitness of the 
    /// entire population then finding a random number inside (0, total populatin fitness)
    /// then summing the individual species until they hit that random numer 
    /// Statistically this allows for species with larger adjusted fitnesses to 
    /// have a greater change of being picked for breeding
    #[inline]
    fn get_biased_random_species<T, E>(&self, r: &mut ThreadRng, families: &Vec<Family<T, E>>) -> Option<Family<T, E>> 
        where 
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync
    {
        // set a result option to none, this will panic! if the result is still none
        // at the end of the function. Then get the total poopulation fitness
        let mut result = None;
        let total = families.iter()
            .fold(0.0, |sum, curr| {
                sum + (*curr).read().unwrap().get_total_adjusted_fitness()
            });

        // iterate through the species until the iterative sum is at or above the selected
        // random adjusted fitness level
        let mut curr = 0.0;
        let index = r.gen::<f32>() * total;
        for i in families.iter() {
            curr += i.read().ok()?.get_total_adjusted_fitness();
            if curr >= index {
                result = Some(Arc::clone(i));
                break
            }
        }
        // either return the result, or panic!
        result.or_else(|| Some(Arc::clone(families.first()?)))
    }



    /// Get a biased random member from the species. By summing the fitness scores of the 
    /// members, members with larger fitness scorese are statistically more likely to be picked
    #[inline]
    pub fn get_biased_random_member<T, E>(&self, r: &mut ThreadRng, family: &Family<T, E>) -> (f32, Member<T>)
        where
            T: Genome<T, E> + Send + Sync + Clone,
            E: Send + Sync
    {
        // declare a result which will panic! at the end of the function if there 
        // is no member found, then get the species total fitness score
        let species_lock = family.read().unwrap();
        let total = species_lock.get_total_adjusted_fitness();
        let index = r.gen::<f32>() * total;
        let (mut result, mut curr) = (None, 0.0);
        // go through each member and see if it's adjusted fitness has pushed it over the edge
        for member in species_lock.members.iter() {
            curr += member.0;
            if curr >= index {
                result = Some(member);
                break
            }
        };
        // either unwrap the result, or if the adjusted fitness of the species was all
        // negative, just take the first member. If the fitness of the species is negative,
        // the algorithm essentially preforms a random search for these biased functions 
        // once the fitness is above 0, it will 'catch on' and start producing biased results
        result.or_else(|| Some(&species_lock.members[0]))
            .and_then(|val| {
                Some((val.0, val.1.clone()
                    .upgrade()
                    .unwrap_or_else(|| panic!("Failed to get random species member."))
                ))
            })
            .unwrap()
    }

}