Skip to main content

lgp/core/
program.rs

1use std::iter::repeat_with;
2
3use crate::utils::random::generator;
4use clap::Args;
5use derivative::Derivative;
6use derive_builder::Builder;
7use rand::{seq::IteratorRandom, Rng};
8
9use serde::{Deserialize, Serialize};
10use tracing::{instrument, trace};
11use uuid::Uuid;
12
13use super::{
14    engines::{
15        breed_engine::{Breed, BreedEngine},
16        freeze_engine::{Freeze, FreezeEngine},
17        generate_engine::{Generate, GenerateEngine},
18        mutate_engine::{Mutate, MutateEngine},
19        reset_engine::{Reset, ResetEngine},
20        status_engine::{Status, StatusEngine},
21    },
22    environment::State,
23    instruction::InstructionGeneratorParameters,
24    instructions::Instructions,
25    registers::Registers,
26};
27
28#[derive(Clone, Debug, Args, Deserialize, Serialize, Derivative, Builder)]
29#[derivative(Copy)]
30pub struct ProgramGeneratorParameters {
31    #[arg(long, default_value = "12")]
32    #[builder(default = "12")]
33    pub max_instructions: usize,
34    #[command(flatten)]
35    pub instruction_generator_parameters: InstructionGeneratorParameters,
36}
37
38impl Reset<Program> for ResetEngine {
39    fn reset(item: &mut Program) {
40        ResetEngine::reset(&mut item.registers);
41        ResetEngine::reset(&mut item.fitness);
42    }
43}
44
45impl Freeze<Program> for FreezeEngine {}
46
47impl Status<Program> for StatusEngine {
48    fn set_fitness(program: &mut Program, fitness: f64) {
49        program.fitness = fitness;
50    }
51
52    fn get_fitness(program: &Program) -> f64 {
53        program.fitness
54    }
55
56    fn valid(item: &Program) -> bool {
57        item.fitness.is_finite()
58    }
59
60    fn evaluated(item: &Program) -> bool {
61        !item.fitness.is_nan()
62    }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, Derivative, Builder)]
66pub struct Program {
67    pub id: Uuid,
68    pub instructions: Instructions,
69    pub registers: Registers,
70    pub fitness: f64,
71}
72
73impl PartialEq for Program {
74    fn eq(&self, other: &Self) -> bool {
75        self.id == other.id
76    }
77}
78
79impl Ord for Program {
80    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
81        f64::total_cmp(&self.fitness, &other.fitness)
82    }
83}
84
85impl Eq for Program {}
86
87impl PartialOrd for Program {
88    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
89        Some(self.cmp(other))
90    }
91}
92
93impl Program {
94    #[instrument(skip_all, fields(program_id = %self.id, n_instructions = self.instructions.len()), level = "trace")]
95    pub fn run(&mut self, input: &impl State) {
96        for (idx, instruction) in self.instructions.iter().enumerate() {
97            trace!(instruction_idx = idx, "Executing instruction");
98            instruction.apply(&mut self.registers, input)
99        }
100    }
101}
102
103impl Generate<ProgramGeneratorParameters, Program> for GenerateEngine {
104    fn generate(using: ProgramGeneratorParameters) -> Program {
105        let ProgramGeneratorParameters {
106            max_instructions,
107            instruction_generator_parameters,
108            ..
109        } = using;
110
111        let registers = Registers::new(
112            instruction_generator_parameters.n_actions,
113            instruction_generator_parameters.n_extras,
114        );
115        let n_instructions = generator().gen_range(1..=max_instructions);
116        let instructions =
117            repeat_with(|| GenerateEngine::generate(instruction_generator_parameters))
118                .take(n_instructions)
119                .collect();
120
121        Program {
122            id: Uuid::new_v4(),
123            instructions,
124            registers,
125            fitness: f64::NAN,
126        }
127    }
128}
129
130impl Mutate<ProgramGeneratorParameters, Program> for MutateEngine {
131    fn mutate(item: &mut Program, using: ProgramGeneratorParameters) {
132        // Pick instruction to mutate.
133        let instruction = item
134            .instructions
135            .iter_mut()
136            .choose(&mut generator())
137            .unwrap();
138
139        MutateEngine::mutate(instruction, using.instruction_generator_parameters);
140
141        ResetEngine::reset(&mut item.id);
142        ResetEngine::reset(item);
143    }
144}
145
146impl Breed<Program> for BreedEngine {
147    fn two_point_crossover(mate_1: &Program, mate_2: &Program) -> (Program, Program) {
148        let (child_1_instructions, child_2_instructions) =
149            BreedEngine::two_point_crossover(&mate_1.instructions, &mate_2.instructions);
150
151        let mut child_1 = mate_1.clone();
152        let mut child_2 = mate_2.clone();
153
154        child_1.instructions = child_1_instructions;
155        child_2.instructions = child_2_instructions;
156
157        ResetEngine::reset(&mut child_1.id);
158        ResetEngine::reset(&mut child_2.id);
159
160        ResetEngine::reset(&mut child_1);
161        ResetEngine::reset(&mut child_2);
162
163        (child_1, child_2)
164    }
165}
166
167#[cfg(test)]
168mod tests {
169
170    use crate::core::instruction::InstructionGeneratorParameters;
171
172    use super::*;
173
174    #[test]
175    fn given_instructions_when_breed_then_two_children_are_produced_using_genes_of_parents() {
176        let params = InstructionGeneratorParameters {
177            n_extras: 1,
178            external_factor: 10.,
179            n_actions: 4,
180            n_inputs: 2,
181        };
182        let instructions_a: Instructions =
183            (0..10).map(|_| GenerateEngine::generate(params)).collect();
184        let instructions_b: Instructions =
185            (0..10).map(|_| GenerateEngine::generate(params)).collect();
186
187        let (child_a, child_b) = BreedEngine::two_point_crossover(&instructions_a, &instructions_b);
188
189        assert_ne!(child_a, child_b);
190
191        assert_ne!(instructions_a, child_a);
192        assert_ne!(instructions_a, child_b);
193
194        assert_ne!(instructions_b, child_a);
195        assert_ne!(instructions_b, child_b);
196    }
197
198    #[test]
199    fn given_programs_when_two_point_crossover_then_two_children_are_produced() {
200        let instruction_generator_parameters = InstructionGeneratorParameters {
201            n_extras: 1,
202            external_factor: 10.,
203            n_actions: 2,
204            n_inputs: 4,
205        };
206        let program_params = ProgramGeneratorParameters {
207            max_instructions: 100,
208            instruction_generator_parameters,
209        };
210
211        let program_a = GenerateEngine::generate(program_params);
212        let program_b = GenerateEngine::generate(program_params);
213
214        let (child_a, child_b) = BreedEngine::two_point_crossover(&program_a, &program_b);
215
216        assert_ne!(child_a, child_b);
217
218        assert_ne!(program_a, child_a);
219        assert_ne!(program_a, child_b);
220
221        assert_ne!(program_b, child_a);
222        assert_ne!(program_b, child_b);
223    }
224}