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
use rand::{seq::SliceRandom, Rng};

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

use super::{MutationError, MutationResult, Mutations};

impl Mutations {
    /// This mutation adds a new feed-forward connection to the genome, should it be possible.
    /// It is possible when any two nodes[^details] are not yet connected with a feed-forward 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_connection(genome: &mut Genome, rng: &mut impl Rng) -> MutationResult {
        let mut possible_start_nodes = genome
            .inputs
            .iter()
            .chain(genome.hidden.iter())
            .collect::<Vec<_>>();
        possible_start_nodes.shuffle(rng);

        let mut possible_end_nodes = genome
            .hidden
            .iter()
            .chain(genome.outputs.iter())
            .collect::<Vec<_>>();
        possible_end_nodes.shuffle(rng);

        for start_node in possible_start_nodes {
            if let Some(end_node) = possible_end_nodes.iter().cloned().find(|&end_node| {
                end_node != start_node
                    && !genome.feed_forward.contains(&Connection::new(
                        start_node.id,
                        0.0,
                        end_node.id,
                    ))
                    && !genome.would_form_cycle(start_node, end_node)
            }) {
                // add new feed-forward connection
                assert!(genome.feed_forward.insert(Connection::new(
                    start_node.id,
                    Connection::weight_perturbation(0.0, 0.1, rng),
                    end_node.id,
                )));
                return Ok(());
            }
        }
        // no possible connection end present
        Err(MutationError::CouldNotAddFeedForwardConnection)
    }
}

#[cfg(test)]
mod tests {
    use rand::thread_rng;

    use crate::{Genome, MutationError, Mutations, Parameters};

    #[test]
    fn add_random_connection() {
        let mut genome = Genome::uninitialized(&Parameters::default());

        assert!(Mutations::add_connection(&mut genome, &mut thread_rng()).is_ok());
        assert_eq!(genome.feed_forward.len(), 1);
    }

    #[test]
    fn dont_add_same_connection_twice() {
        let mut genome = Genome::uninitialized(&Parameters::default());

        Mutations::add_connection(&mut genome, &mut thread_rng()).expect("add_connection");

        if let Err(error) = Mutations::add_connection(&mut genome, &mut thread_rng()) {
            assert_eq!(error, MutationError::CouldNotAddFeedForwardConnection);
        } else {
            unreachable!()
        }

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