Skip to main content

lgp/core/
instruction.rs

1use clap::Args;
2use derivative::Derivative;
3use derive_builder::Builder;
4use rand::distributions::Standard;
5use rand::prelude::Distribution;
6use rand::Rng;
7use serde::{Deserialize, Serialize};
8use std::fmt::Debug;
9use tracing::trace;
10
11use crate::utils::random::generator;
12
13use super::engines::generate_engine::{Generate, GenerateEngine};
14use super::engines::mutate_engine::{Mutate, MutateEngine};
15use super::environment::State;
16use super::registers::Registers;
17use derive_more::Display;
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Copy, Deserialize)]
20pub enum Mode {
21    External,
22    Internal,
23}
24
25#[derive(Clone, Copy, Debug, Display, Serialize, PartialEq, Eq, Deserialize)]
26pub enum Op {
27    #[display(fmt = "+")]
28    Add,
29    #[display(fmt = "*")]
30    Mult,
31    #[display(fmt = "/")]
32    Divide,
33    #[display(fmt = "-")]
34    Sub,
35}
36
37impl Op {
38    pub fn apply(&self, a: f64, b: f64) -> f64 {
39        match *self {
40            Op::Add => a + b,
41            Op::Mult => a * b,
42            Op::Divide => a / b, // Division by zero produces inf/NaN, causing program failure
43            Op::Sub => a - b,
44        }
45    }
46}
47
48impl Distribution<Op> for Standard {
49    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Op {
50        match rng.gen_range(0..=3) {
51            0 => Op::Add,
52            1 => Op::Mult,
53            2 => Op::Divide,
54            _ => Op::Sub,
55        }
56    }
57}
58
59impl Distribution<Mode> for Standard {
60    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Mode {
61        match rng.gen_bool(0.5) {
62            false => Mode::External,
63            true => Mode::Internal,
64        }
65    }
66}
67
68#[derive(Clone, Derivative, Debug, Serialize, Args, PartialEq, Deserialize, Builder)]
69#[derivative(Copy)]
70pub struct InstructionGeneratorParameters {
71    #[arg(long, default_value = "1")]
72    #[builder(default = "1")]
73    pub n_extras: usize,
74    #[arg(long, default_value = "10.")]
75    #[builder(default = "10.")]
76    pub external_factor: f64,
77    #[arg(skip)]
78    pub n_actions: usize,
79    #[arg(skip)]
80    pub n_inputs: usize,
81}
82
83impl InstructionGeneratorParameters {
84    pub fn n_registers(&self) -> usize {
85        // Mountain Car Example: | -1 | 0 | 1 | Extra |
86        self.n_actions + self.n_extras
87    }
88}
89
90#[derive(Serialize, PartialEq, Debug, Deserialize, Derivative)]
91#[derivative(Copy, Clone)]
92pub struct Instruction {
93    src_idx: usize,
94    tgt_idx: usize,
95    mode: Mode,
96    op: Op,
97    external_factor: f64,
98}
99
100impl Generate<InstructionGeneratorParameters, Instruction> for GenerateEngine {
101    fn generate(using: InstructionGeneratorParameters) -> Instruction {
102        let src_idx = generator().gen_range(0..using.n_registers());
103
104        let mode = generator().gen();
105
106        let upper_bound_target_index = if mode == Mode::External {
107            using.n_inputs
108        } else {
109            using.n_registers()
110        };
111
112        let target_index = generator().gen_range(0..upper_bound_target_index);
113
114        let executable = generator().gen();
115
116        Instruction {
117            src_idx,
118            tgt_idx: target_index,
119            mode,
120            op: executable,
121            external_factor: using.external_factor,
122        }
123    }
124}
125
126impl Mutate<InstructionGeneratorParameters, Instruction> for MutateEngine {
127    fn mutate(instruction: &mut Instruction, using: InstructionGeneratorParameters) {
128        let mutated = GenerateEngine::generate(using);
129
130        let swap_target = generator().gen();
131        let swap_source = generator().gen();
132        let swap_exec = generator().gen();
133
134        // Flip a Coin: Target
135        if swap_target {
136            instruction.mode = mutated.mode;
137            instruction.tgt_idx = mutated.tgt_idx;
138        }
139
140        // Flip a Coin: Source
141        if swap_source {
142            instruction.src_idx = mutated.src_idx;
143        }
144
145        // Flip a Coin: Executable
146        if swap_exec {
147            instruction.op = mutated.op;
148        }
149    }
150}
151
152impl Instruction {
153    pub fn apply(&self, registers: &mut Registers, input: &impl State) {
154        let target_value = match self.mode {
155            Mode::External => self.external_factor * input.get_value(self.tgt_idx),
156            _ => *registers.get(self.tgt_idx),
157        };
158
159        let source_value = *registers.get(self.src_idx);
160        let new_source_value = self.op.apply(source_value, target_value);
161
162        trace!(
163            op = %self.op,
164            src_idx = self.src_idx,
165            tgt_idx = self.tgt_idx,
166            mode = ?self.mode,
167            source_value = source_value,
168            target_value = target_value,
169            result = new_source_value,
170            "Instruction applied"
171        );
172
173        registers.update(self.src_idx, new_source_value);
174    }
175}