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
use rand::Rng;

use crate::{genes::Connection, genome::Genome, rng::GenomeRng};

use super::Mutations;

impl Mutations {
    /// This mutation adds a recurrent connection to the `genome` when possible.
    /// It is possible when any two nodes [^details] are not yet connected with a recurrent connection.
    ///
    /// [^details]: "any two nodes" is technically not correct as the start node for the connection has to come from the intersection of input and hidden nodes and the end node has to come from the intersection of the hidden and output nodes.
    pub fn add_recurrent_connection(
        genome: &mut Genome,
        rng: &mut GenomeRng,
    ) -> Result<(), &'static str> {
        let start_node_iterator = genome.inputs.iter().chain(genome.hidden.iter());
        let end_node_iterator = genome.hidden.iter().chain(genome.outputs.iter());

        for start_node in start_node_iterator
            // make iterator wrap
            .cycle()
            // randomly offset into the iterator to choose any node
            .skip(
                (rng.gen::<f64>() * (genome.inputs.len() + genome.hidden.len()) as f64).floor()
                    as usize,
            )
            // just loop every value once
            .take(genome.inputs.len() + genome.hidden.len())
        {
            if let Some(end_node) = end_node_iterator.clone().find(|&end_node| {
                end_node != start_node
                    && !genome
                        .recurrent
                        .contains(&Connection::new(start_node.id, 0.0, end_node.id))
            }) {
                assert!(genome.recurrent.insert(Connection::new(
                    start_node.id,
                    rng.weight_perturbation(0.0),
                    end_node.id,
                )));
                return Ok(());
            }
            // no possible connection end present
        }
        Err("no connection possible")
    }
}

#[cfg(test)]
mod tests {
    use crate::GenomeContext;

    #[test]
    fn add_random_connection() {
        let mut gc = GenomeContext::default();

        let mut genome = gc.initialized_genome();

        assert!(genome
            .add_recurrent_connection_with_context(&mut gc)
            .is_ok());

        assert_eq!(genome.recurrent.len(), 1);
    }

    #[test]
    fn dont_add_same_connection_twice() {
        let mut gc = GenomeContext::default();

        let mut genome = gc.initialized_genome();

        assert!(genome
            .add_recurrent_connection_with_context(&mut gc)
            .is_ok());

        if let Err(message) = genome.add_connection_with_context(&mut gc) {
            assert_eq!(message, "no connection possible");
        } else {
            unreachable!()
        }

        assert_eq!(genome.recurrent.len(), 1);
    }
}