use crate::function_set::{FunctionSet, Symbol};
use super::alphabet::Alphabet;
use super::tree::ExpressionTree;
pub trait GenotypePhenotypeMap<F: FunctionSet> {
#[must_use]
fn decode(&self, alphabet: &Alphabet<F>, genome: &[Symbol]) -> ExpressionTree;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct GepDecoder;
impl<F: FunctionSet> GenotypePhenotypeMap<F> for GepDecoder {
fn decode(&self, alphabet: &Alphabet<F>, genome: &[Symbol]) -> ExpressionTree {
if genome.is_empty() {
return ExpressionTree::from_parts(Vec::new(), Vec::new(), Vec::new());
}
let mut needed: usize = 1;
let mut orf_len = 0;
while needed > 0 && orf_len < genome.len() {
let arity = alphabet.arity(genome[orf_len]);
needed = needed - 1 + arity;
orf_len += 1;
}
debug_assert!(
needed == 0,
"genome violates GEP head/tail invariant t = h(n-1)+1 (Ferreira \
2001 eq. 3.4): {needed} child slot(s) left unfilled"
);
let nodes: Vec<Symbol> = genome[..orf_len].to_vec();
let mut arities = Vec::with_capacity(orf_len);
let mut child_start = Vec::with_capacity(orf_len);
let mut read = 1usize;
for &symbol in &nodes {
let arity = alphabet.arity(symbol);
arities.push(arity);
child_start.push(read);
read += arity;
}
ExpressionTree::from_parts(nodes, arities, child_start)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::function_set::ArithmeticFunctionSet;
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
fn alphabet(n_vars: usize) -> Alphabet<ArithmeticFunctionSet> {
Alphabet::new(ArithmeticFunctionSet, n_vars, vec![])
}
#[test]
fn orf_length_for_nested_tree() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(0),
Symbol::from_raw(2),
Symbol::from_raw(8),
Symbol::from_raw(8),
Symbol::from_raw(8),
Symbol::from_raw(8), Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 5);
}
#[test]
fn decode_is_deterministic() {
let a = alphabet(2);
let genome = vec![
Symbol::from_raw(0),
Symbol::from_raw(1),
Symbol::from_raw(8),
Symbol::from_raw(9),
Symbol::from_raw(8),
Symbol::from_raw(9),
Symbol::from_raw(8),
];
let t1 = GepDecoder.decode(&a, &genome);
let t2 = GepDecoder.decode(&a, &genome);
assert_eq!(t1.nodes(), t2.nodes());
assert_eq!(t1.node_count(), t2.node_count());
}
#[test]
fn all_terminal_head_is_one_node() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(8),
Symbol::from_raw(8),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 1);
}
#[test]
fn empty_genome_decodes_to_zero_node_tree() {
let a = alphabet(1);
let tree = GepDecoder.decode(&a, &[]);
assert_eq!(tree.node_count(), 0);
approx::assert_relative_eq!(tree.eval(&a, &[1.0]), 0.0, epsilon = 1e-6);
}
#[test]
fn arity_one_function_decodes_two_nodes() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(4),
Symbol::from_raw(8),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 2);
approx::assert_relative_eq!(tree.eval(&a, &[0.0]), 0.0f32.sin(), epsilon = 1e-6);
}
#[test]
fn full_tail_deep_tree() {
let a = alphabet(1);
let mut genome = vec![Symbol::from_raw(0); 4];
genome.extend(std::iter::repeat_n(Symbol::from_raw(8), 5));
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 9);
approx::assert_relative_eq!(tree.eval(&a, &[2.0]), 10.0, epsilon = 1e-6);
}
#[test]
fn out_of_range_symbol_is_terminal() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(999),
Symbol::from_raw(8),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 1);
approx::assert_relative_eq!(tree.eval(&a, &[3.0]), 0.0, epsilon = 1e-6);
}
#[test]
fn wellformed_genomes_always_decode_in_bounds() {
let mut rng = StdRng::seed_from_u64(0x9E37_79B9_7F4A_7C15);
let max_arity = 2; for n_vars in 1..=3usize {
let alpha = alphabet(n_vars);
for _ in 0..2_000 {
let head_len = 1 + rng_usize(&mut rng, 12); let tail_len = head_len * (max_arity - 1) + 1; let mut genome = Vec::with_capacity(head_len + tail_len);
for _ in 0..head_len {
genome.push(alpha.sample_head_symbol(&mut rng));
}
for _ in 0..tail_len {
genome.push(alpha.sample_tail_symbol(&mut rng));
}
let tree = GepDecoder.decode(&alpha, &genome);
let node_count = tree.node_count();
assert!(node_count >= 1, "coding region must be non-empty");
let total_children: usize = tree.nodes().iter().map(|&sym| alpha.arity(sym)).sum();
assert_eq!(
total_children + 1,
node_count,
"well-formed genome left an unfilled child slot: {genome:?}"
);
let inputs: Vec<f32> = (0..n_vars).map(|_| rng_input(&mut rng)).collect();
let value = tree.eval(&alpha, &inputs);
assert!(value.is_finite(), "eval produced non-finite {value}");
}
}
}
fn rng_usize(rng: &mut StdRng, bound: usize) -> usize {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let hi = bound as i32;
#[allow(clippy::cast_sign_loss)]
{
rng.random_range(0..hi) as usize
}
}
fn rng_input(rng: &mut StdRng) -> f32 {
rng.random_range(-1.0e6f32..1.0e6f32)
}
}