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
//! Option types for the EANT2 algorithm.

use cmaes::options::CMAESEndConditions;
use cge::{Network, TransferFunction};

const DEFAULT_CMAES_CONDITIONS: [CMAESEndConditions; 2] = [
    CMAESEndConditions::StableGenerations(0.0001, 5),
    CMAESEndConditions::MaxGenerations(500)
];

pub const DEFAULT_POPULATION_SIZE: usize = 10;
pub const DEFAULT_OFFSPRING_COUNT: usize = 4;
pub const DEFAULT_SIMILAR_FITNESS: f64 = 0.15;
pub const DEFAULT_MIN_FITNESS: f64 = 0.0;
pub const DEFAULT_MAX_GENERATIONS: usize = 30;
pub const DEFAULT_THREADS: usize = 0;
pub const DEFAULT_CMAES_RUNS: usize = 2;
pub const DEFAULT_PRINT_OPTION: bool = false;
pub const DEFAULT_WEIGHTS: [usize; 4] = [3, 8, 1, 3];
pub const DEFAULT_SEED: Option<Network> = None;
pub const DEFAULT_TRANSFER_FUNCTION: TransferFunction = TransferFunction::Sigmoid;

/// A container for all parameters and options for the EANT2 algorithm. See constants for default
/// values.
///
/// Using default options is convenient, but may slightly reduce the quality of the neural networks
/// generated, and can increase the time needed to find a solution. It is recommended to adjust the
/// parameters depending on whether it is more important to find a solution quickly or to find a
/// better solution. Note that increasing population size and CMA-ES runs will only affect the
/// quality a small amount, so using smaller values will greatly speed up the algorithm, and does
/// not have a large downside. It is important to set the fitness threshold using the
/// `fitness_threshold` method, otherwise the algorithm may terminate too soon or take a long time to
/// run. Here are some general tips for setting the options (more in the documentation):
/// 
/// If a minimal neural network is preferred, increase `similar_fitness` and increase the weight for
/// connection removal. For complex problems, decrease population size and the weights for adding
/// connections and neurons.
///
/// # Examples
///
/// ```
/// use eant2::*;
///
/// // A set of default options, with 2 inputs and 3 outputs to each neural network
/// let options = EANT2Options::new(2, 3);
///
/// // A set of options with 4 CMA-ES optimizations per individual, a minimum fitness of 10.0, and
/// // a population size of 50
/// let options = EANT2Options::new(2, 3)
///     .fitness_threshold(10.0)
///     .fitness_threshold(0.0)
///     .cmaes_runs(4)
///     .population_size(50);
/// ```
#[derive(Clone)]
pub struct EANT2Options {
    pub inputs: usize,
    pub outputs: usize,
    pub population_size: usize,
    pub offspring_count: usize,
    pub fitness_threshold: f64,
    pub similar_fitness: f64,
    pub max_generations: usize,
    pub threads: usize,
    pub cmaes_runs: usize,
    pub cmaes_end_conditions: Vec<CMAESEndConditions>,
    pub print_option: bool,
    pub weights: [usize; 4],
    pub seed: Option<Network>,
    pub transfer_function: TransferFunction,
}

impl EANT2Options {
    /// Returns a set of default options (see constants for default values).
    pub fn new(inputs: usize, outputs: usize) -> EANT2Options {
        if inputs == 0 || outputs == 0 {
            panic!("Neural network inputs and outputs cannot be zero");
        }

        EANT2Options {
            inputs: inputs,
            outputs: outputs,
            population_size: DEFAULT_POPULATION_SIZE,
            offspring_count: DEFAULT_OFFSPRING_COUNT,
            fitness_threshold: DEFAULT_MIN_FITNESS,
            similar_fitness: DEFAULT_SIMILAR_FITNESS,
            max_generations: DEFAULT_MAX_GENERATIONS,
            threads: DEFAULT_THREADS,
            cmaes_runs: DEFAULT_CMAES_RUNS,
            cmaes_end_conditions: DEFAULT_CMAES_CONDITIONS.to_vec(),
            print_option: DEFAULT_PRINT_OPTION,
            weights: DEFAULT_WEIGHTS,
            seed: DEFAULT_SEED,
            transfer_function: DEFAULT_TRANSFER_FUNCTION
        }
    }

    /// Add docs here
    pub fn seed(mut self, network: Network) -> EANT2Options {
        self.seed = Some(network);
        self
    }

    /// Sets the population size. Increasing this option may produce higher quality neural
    /// networks, but will increase the time needed to find a solution.
    pub fn population_size(mut self, size: usize) -> EANT2Options {
        if size == 0 {
            panic!("Population size cannot be zero");
        }

        self.population_size = size;
        self
    }

    /// Sets the offspring count (how many offspring each individual spawns). Increasing this
    /// option may produce higher quality neural networks, but will increase the time needed to
    /// find a solution.
    pub fn offspring_count(mut self, count: usize) -> EANT2Options {
        self.offspring_count = count;
        self
    }

    /// Sets the fitness threshold. The algorithm terminates if the best individual has a fitness
    /// less than or equal to the specified amount. It is recommended to set this option to a value
    /// specific to each fitness function. The specified fitness may not be reached if the max
    /// generation is reached first.
    pub fn fitness_threshold(mut self, fitness: f64) -> EANT2Options {
        self.fitness_threshold = fitness;
        self
    }

    /// Sets the maximum generations. The algorithm terminates after the specified number of
    /// generations pass. Increasing this option will likely produce higher quality networks, but
    /// will increase the running time of the algorithm. The number of generations specified may
    /// not be reached if the fitness threshold is reached first.
    pub fn max_generations(mut self, generations: usize) -> EANT2Options {
        self.max_generations = generations;
        self
    }

    /// Sets the threshold for deciding whether two neural networks have a similar fitness.
    /// Increasing this option will make the algorithm more aggresively prefer smaller
    /// neural networks. Decreasing it will do the opposite, allowing larger individuals to stay in
    /// the population. It is recommended to set this option higher if a small neural network is
    /// preferred. The downside is it will take slightly longer to find a solution, due to more
    /// higher fitness neural networks being discarded.
    pub fn similar_fitness(mut self, threshold: f64) -> EANT2Options {
        self.similar_fitness = threshold;
        self
    }

    /// Sets the number of threads to use in the algorithm. Increasing this option on multi-core
    /// hardware will greatly decrease the running time of the algorithm.
    pub fn threads(mut self, threads: usize) -> EANT2Options {
        if threads == 0 {
            panic!("Threads cannot be zero");
        }

        self.threads = threads;
        self
    }

    /// Sets the number of times CMA-ES should be run on each individual. More runs will produce
    /// higher quality neural networks faster at the cost of a higher number of fitness function calls.
    /// Increase the number of runs if the fitness function is cheap to run, or if time taken to
    /// find a solution is not important.
    pub fn cmaes_runs(mut self, runs: usize) -> EANT2Options {
        if runs == 0 {
            panic!("CMA-ES runs cannot be zero");
        }

        self.cmaes_runs = runs;
        self
    }

    /// Sets the end conditions of CMA-ES. Setting higher stable generations or max generations or
    /// evaluations can produce higher quality neural networks at the cost of a longer time taken
    /// to find a solution. Decreasing these options is a good idea when it is important to find a
    /// solution quickly. DO NOT set the fitness threshold option, or it will take a very long time
    /// to find a solution, even on the simplest problems! It is recommended to leave this option alone, but
    /// the documentation for it is available [here][1].
    ///
    /// [1]: http://pengowen123.github.io/cmaes/cmaes/options/enum.CMAESEndConditions.html
    ///
    /// # Examples
    ///
    /// ```
    /// use eant2::*;
    ///
    /// let cmaes_conditions = vec![CMAESEndConditions::MaxGenerations(250),
    ///                             CMAESEndConditions::StableGenerations(0.001, 5)];
    ///
    /// let options = EANT2Options::new(2, 2)
    ///                   .cmaes_end_conditions(cmaes_conditions);
    pub fn cmaes_end_conditions(mut self, conditions: Vec<CMAESEndConditions>) -> EANT2Options {
        self.cmaes_end_conditions = conditions;
        self
    }

    /// Sets whether to print info while the algorithm is running. The generation number is
    /// printed, and info about the solution is printed when the algorithm terminates.
    pub fn print(mut self, print: bool) -> EANT2Options {
        self.print_option = print;
        self
    }

    /// Sets the weight for choosing each mutation. The first element should be the weight for
    /// adding a connection, the second for removing a connection, the third for adding a
    /// neuron, and the fourth for adding a bias input. The weights are relative, so with the
    /// weights `[1, 2, 4, 8]`, each mutation has twice the chance of the previous. In general,
    /// the chance for connection mutations should be higher than the chance for a neuron mutation.
    /// If a minimal network is desired, set the bias, neuron and connection addition weights low,
    /// and the connection removal rate high. For complex problems where network size isn't an
    /// issue, high connection addition rate is a good idea.
    pub fn mutation_weights(mut self, weights: [usize; 4]) -> EANT2Options {
        self.weights = weights;
        self
    }

    /// Sets the transfer function to use for the trained neural networks. See the documentation
    /// [here][1] for information.
    ///
    /// [1]: http://pengowen123.github.io/cge/cge/transfer/enum.TransferFunction.html
    pub fn transfer_function(mut self, function: TransferFunction) -> EANT2Options {
        self.transfer_function = function;
        self
    }
}