use std::iter::repeat_with;
use crate::utils::random::generator;
use clap::Args;
use derivative::Derivative;
use derive_builder::Builder;
use rand::{seq::IteratorRandom, Rng};
use serde::{Deserialize, Serialize};
use tracing::{instrument, trace};
use uuid::Uuid;
use super::{
engines::{
breed_engine::{Breed, BreedEngine},
freeze_engine::{Freeze, FreezeEngine},
generate_engine::{Generate, GenerateEngine},
mutate_engine::{Mutate, MutateEngine},
reset_engine::{Reset, ResetEngine},
status_engine::{Status, StatusEngine},
},
environment::State,
instruction::InstructionGeneratorParameters,
instructions::Instructions,
registers::Registers,
};
#[derive(Clone, Debug, Args, Deserialize, Serialize, Derivative, Builder)]
#[derivative(Copy)]
pub struct ProgramGeneratorParameters {
#[arg(long, default_value = "12")]
#[builder(default = "12")]
pub max_instructions: usize,
#[command(flatten)]
pub instruction_generator_parameters: InstructionGeneratorParameters,
}
impl Reset<Program> for ResetEngine {
fn reset(item: &mut Program) {
ResetEngine::reset(&mut item.registers);
ResetEngine::reset(&mut item.fitness);
}
}
impl Freeze<Program> for FreezeEngine {}
impl Status<Program> for StatusEngine {
fn set_fitness(program: &mut Program, fitness: f64) {
program.fitness = fitness;
}
fn get_fitness(program: &Program) -> f64 {
program.fitness
}
fn valid(item: &Program) -> bool {
item.fitness.is_finite()
}
fn evaluated(item: &Program) -> bool {
!item.fitness.is_nan()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Derivative, Builder)]
pub struct Program {
pub id: Uuid,
pub instructions: Instructions,
pub registers: Registers,
pub fitness: f64,
}
impl PartialEq for Program {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Ord for Program {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
f64::total_cmp(&self.fitness, &other.fitness)
}
}
impl Eq for Program {}
impl PartialOrd for Program {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Program {
#[instrument(skip_all, fields(program_id = %self.id, n_instructions = self.instructions.len()), level = "trace")]
pub fn run(&mut self, input: &impl State) {
for (idx, instruction) in self.instructions.iter().enumerate() {
trace!(instruction_idx = idx, "Executing instruction");
instruction.apply(&mut self.registers, input)
}
}
}
impl Generate<ProgramGeneratorParameters, Program> for GenerateEngine {
fn generate(using: ProgramGeneratorParameters) -> Program {
let ProgramGeneratorParameters {
max_instructions,
instruction_generator_parameters,
..
} = using;
let registers = Registers::new(
instruction_generator_parameters.n_actions,
instruction_generator_parameters.n_extras,
);
let n_instructions = generator().gen_range(1..=max_instructions);
let instructions =
repeat_with(|| GenerateEngine::generate(instruction_generator_parameters))
.take(n_instructions)
.collect();
Program {
id: Uuid::new_v4(),
instructions,
registers,
fitness: f64::NAN,
}
}
}
impl Mutate<ProgramGeneratorParameters, Program> for MutateEngine {
fn mutate(item: &mut Program, using: ProgramGeneratorParameters) {
let instruction = item
.instructions
.iter_mut()
.choose(&mut generator())
.unwrap();
MutateEngine::mutate(instruction, using.instruction_generator_parameters);
ResetEngine::reset(&mut item.id);
ResetEngine::reset(item);
}
}
impl Breed<Program> for BreedEngine {
fn two_point_crossover(mate_1: &Program, mate_2: &Program) -> (Program, Program) {
let (child_1_instructions, child_2_instructions) =
BreedEngine::two_point_crossover(&mate_1.instructions, &mate_2.instructions);
let mut child_1 = mate_1.clone();
let mut child_2 = mate_2.clone();
child_1.instructions = child_1_instructions;
child_2.instructions = child_2_instructions;
ResetEngine::reset(&mut child_1.id);
ResetEngine::reset(&mut child_2.id);
ResetEngine::reset(&mut child_1);
ResetEngine::reset(&mut child_2);
(child_1, child_2)
}
}
#[cfg(test)]
mod tests {
use crate::core::instruction::InstructionGeneratorParameters;
use super::*;
#[test]
fn given_instructions_when_breed_then_two_children_are_produced_using_genes_of_parents() {
let params = InstructionGeneratorParameters {
n_extras: 1,
external_factor: 10.,
n_actions: 4,
n_inputs: 2,
};
let instructions_a: Instructions =
(0..10).map(|_| GenerateEngine::generate(params)).collect();
let instructions_b: Instructions =
(0..10).map(|_| GenerateEngine::generate(params)).collect();
let (child_a, child_b) = BreedEngine::two_point_crossover(&instructions_a, &instructions_b);
assert_ne!(child_a, child_b);
assert_ne!(instructions_a, child_a);
assert_ne!(instructions_a, child_b);
assert_ne!(instructions_b, child_a);
assert_ne!(instructions_b, child_b);
}
#[test]
fn given_programs_when_two_point_crossover_then_two_children_are_produced() {
let instruction_generator_parameters = InstructionGeneratorParameters {
n_extras: 1,
external_factor: 10.,
n_actions: 2,
n_inputs: 4,
};
let program_params = ProgramGeneratorParameters {
max_instructions: 100,
instruction_generator_parameters,
};
let program_a = GenerateEngine::generate(program_params);
let program_b = GenerateEngine::generate(program_params);
let (child_a, child_b) = BreedEngine::two_point_crossover(&program_a, &program_b);
assert_ne!(child_a, child_b);
assert_ne!(program_a, child_a);
assert_ne!(program_a, child_b);
assert_ne!(program_b, child_a);
assert_ne!(program_b, child_b);
}
}