moonlander_gp/
random_pop.rs

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
use std::cmp::max;
use super::{AstNode, Population, Fitness, Mutatable, Number};
use rand::Rng;
use rustc_serialize::Encodable;

/// Implement trait to generate random subtrees of a given type.
///
/// This trait is like `rand::Rand`, but it doesn't require that the `Rng`
/// instance is `Sized`, so that we can combine it with the `Mutatable` trait.
///
/// It also takes a parameter that controls the height of trees that will be
/// randomly generated. The `NodeWeights` parameter will indicate the relative
/// weights to be used when deciding between generating internal vs. leaf nodes
/// in the tree.
pub trait RandNode: Sized {
    fn rand(weights: NodeWeights, rng: &mut Rng) -> Self;
}

impl <T: RandNode+AstNode> Mutatable for T {
    fn mutate(&self, max_height: i32, rng: &mut Rng) -> Box<AstNode> {
        Box::new(T::rand(NodeWeights::fixed(max_height), rng))
    }
}

/// Generate a random population of size N.
///
/// Trees of various depths will be generated in a uniform fashion between
/// 1 and `max_depth`.
pub fn random_population<P: RandNode+Clone+Sync, F: Fitness+Sized+Send, R: Rng>(n: usize, max_depth: usize, rng: &mut R) -> Population<P, F> {
    let mut ret = Population::new(n, 0);
    for i in 0..n {
        let height = 1 + i / (n / max_depth);
        ret.add(P::rand(NodeWeights::fixed(height as i32), rng));
    }
    ret
}

/// Take the best fraction of the population and fill back up to N with random
/// programs.
pub fn retain_best<P, F, R>(frac: Number, pop: Population<P, F>, max_depth: usize, rng: &mut R) -> Population<P, F>
    where P: RandNode+Clone+Sync+Encodable,
          F: Fitness+Sized+Send+Encodable,
          R: Rng
{
    let n = (pop.n() as Number * frac) as usize;
    let filler = pop.n() - n;
    let mut ret = Population::new(n, 0);
    ret.generation = pop.generation + 1;

    for c in pop.best_n(n) {
        ret.add(c);
    }

    for i in 0..filler {
        let height = 1 + i / (filler / max_depth);
        ret.add(P::rand(NodeWeights::fixed(height as i32), rng));
    }
    ret
}

/// Weights to use when deciding between internal and leaf nodes.
///
/// This structure is initialized with a desired target depth, and
/// every level advancing in the tree shifts the weights away from
/// internal nodes and towards leaf nodes.
#[derive(Copy,Clone)]
pub struct NodeWeights {
    current_level: i32,
    per_level: i32
}

impl NodeWeights {
    pub fn fixed(target_height: i32) -> NodeWeights {
        NodeWeights {
            current_level: 0,
            per_level: 100 / max(target_height - 1, 1)
        }
    }

    pub fn randomized(max_height: i32, rng: &mut Rng) -> NodeWeights {
        NodeWeights::fixed(1 + rng.next_u32() as i32 % (max_height - 1))
    }

    pub fn internal(&self) -> u32 {
        max(100 - self.per_level * self.current_level, MIN_WEIGHT) as u32
    }

    pub fn leaf(&self) -> u32 {
        max(self.per_level * self.current_level, MIN_WEIGHT) as u32
    }

    pub fn next_level(&self) -> NodeWeights {
        NodeWeights { current_level: self.current_level + 1, per_level: self.per_level }
    }

    pub fn gen_child<P: RandNode>(&self, rng: &mut Rng) -> Box<P> {
        Box::new(P::rand(self.next_level(), rng))
    }
}

/// Minimum weight
///
/// We need at least some weight, in case we need to make a leaf node
/// but only internal nodes are available at that point in the tree.
const MIN_WEIGHT : i32 = 1;


#[cfg(test)]
mod tests {
    use super::*;
    use super::super::{num, Number, depth};
    use super::super::genetic::mutate_tree;

    #[derive(Clone)]
    enum List {
        Cons(Box<List>),
        Nil
    }

    impl_astnode!(List, 0,
                  int Cons(next),
                  leaf Nil());

    #[test]
    fn test_node_heights_on_generation() {
        let target_height = 8;
        let n = 1000;
        let mut rng = ::rand::StdRng::new().unwrap();

        let weights = NodeWeights::fixed(target_height);
        let programs : Vec<List> = (0..n).map(|_| RandNode::rand(weights, &mut rng)).collect();
        let avg_height = num::sum(programs.iter().map(|p| depth(p) as Number)) / n as Number;

        // Since we have uniform distribution of heights, the average will be about 0.5
        // times the max height. Some fudge margin for randomness.
        assert!(avg_height <= 0.6 * target_height as Number);
    }

    #[test]
    fn test_node_heights_during_mutation() {
        // Check that during mutation, the program doesn't grow endlessly
        let n = 1000;
        let target_height = 8;
        let mut rng = ::rand::StdRng::new().unwrap();
        let weights = NodeWeights::fixed(target_height);

        let mut program : List = RandNode::rand(weights, &mut rng);
        for _ in 0..n {
            program = *mutate_tree(&program, target_height, &mut rng);
            let depth = depth(&program);
            println!("Depth: {}", depth);
            // Check that we didn't grow too large, plus a fudge factor
            assert!((depth as Number) < target_height as Number * 1.5);
        }
    }
}