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 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}