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
pub mod population;
pub mod generation;
pub mod niche;
pub mod genocide;
pub mod survival;



/// Genome is what will actually be evolved through the engine, 
/// this is going to be whatever datastructure should be optimized
pub mod genome {
    
    use super::environment::Envionment;
    use std::marker::Sized;
    use std::sync::{Arc, RwLock};

    pub trait Genome<T, E>
        where
            T: ?Sized + Send + Sync,
            E: ?Sized + Send + Sync
    {
        /// Crossover is the process of taking two types (T) and returning 
        /// a new type, this is done through some defined form of 
        /// mutation using the config type, or through crossover 
        /// where parts of one type are given to parts of the other and that resulting
        /// type is returnd
        fn crossover(one: &T, two: &T, env: &Arc<RwLock<E>>, crossover_rate: f32) -> Option<T> 
            where 
                T: Sized,
                E: Envionment + Sized;
        
                /// This is a measure of a evolutionary types structure or topology - depending on what is being evolved.
        /// This is needed to split the members in their respective species - essentially it is 
        /// a measure of how far away two types are from each other in a genetic 
        /// sense. Think of something like how similar humans are to dolphins, this is a way to quanitfy that.
        fn distance(one: &T, two: &T, env: &Arc<RwLock<E>>) -> f32;
        
        /// Genome needs to have a base implementation in order for one of the population options to be satisfied
        /// 
        /// This can probably be iplemented in a generic way for default if the user doesn't want to 
        /// implement it for their problem. 
        fn base(_: &mut E) -> T
            where T: Sized
        {
            panic!("Base not implemented.");
        }
    }
}



/// Environment represents overall settings for a genome, this can be statistics to be 
/// tracked through evolution, or things like mutation rates or global counters. This is 
/// injected into functions throughou the generational process so it is acessable globally as a
/// center point for the evolution. Note - if this is to be used a mutable in crossover or mutation, 
/// this will slow down the optimization process as it will have to be locked during the writing thus
/// having the vairiables in the implementatino of this trait be readonly is preferred but isn't that big of a deal
pub mod environment {
    pub trait Envionment {
        
        /// Reset can be used to reset the enviromnet after a certain event occurs,
        /// if not this is an empty default implementation
        fn reset(&mut self) { }
    
    }
}



/// Problem is the actual problem to be solved.
/// This is wrapped in an Arc pointer due to the problem not wanting to be 
/// coppied through threads. This was done intentially because I wanted to be able to 
/// represent supervised, unsupervised, and general reinforcement learning problems. This
/// means if you are using a supervised system and have a large dataset to analyze, if this 
/// dataset is stored in the problem (as they should be), without an Arc pointer this large dataset would 
/// be coppied multiple times and take up massive amounts of memory. The Arc allows us to keep only one version
/// of the problem and share that between threads. Note - this means everything in the problem and all it's data
/// is explicitly readonly 
pub mod problem {

    pub trait Problem<T> {

        /// empty can be a new for Self, or some sort of default value,
        /// just needed to create a population with base parameters 
        fn empty() -> Self;
        
        /// Solve is what actually solves the problem , given a solver (the genome type)
        /// use the data in the type implementing the problem to sovle the problem and return
        /// the member's score. The result of this function is the member's fitness score 
        fn solve(&self, member: &mut T) -> f32;
    }
}