use crate::function_set::{FunctionSet, Symbol};
use super::alphabet::{Alphabet, SymbolKind};
const EVAL_CLAMP: f32 = 1e15;
#[must_use]
#[inline]
fn finite_or_clamp(v: f32) -> f32 {
if v.is_nan() {
0.0
} else {
v.clamp(-EVAL_CLAMP, EVAL_CLAMP)
}
}
#[derive(Clone, Debug)]
pub struct ExpressionTree {
nodes: Vec<Symbol>,
arities: Vec<usize>,
child_start: Vec<usize>,
}
impl ExpressionTree {
#[must_use]
pub(super) fn from_parts(
nodes: Vec<Symbol>,
arities: Vec<usize>,
child_start: Vec<usize>,
) -> Self {
debug_assert_eq!(nodes.len(), arities.len());
debug_assert_eq!(nodes.len(), child_start.len());
Self {
nodes,
arities,
child_start,
}
}
#[must_use]
pub fn node_count(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn nodes(&self) -> &[Symbol] {
&self.nodes
}
#[must_use]
pub fn depth(&self) -> usize {
let n = self.nodes.len();
if n == 0 {
return 0;
}
let mut node_depth = vec![0usize; n];
for i in (0..n).rev() {
let arity = self.arities[i];
if arity == 0 {
node_depth[i] = 0;
} else {
let start = self.child_start[i];
let mut max_child = 0;
for k in 0..arity {
max_child = max_child.max(node_depth[start + k]);
}
node_depth[i] = 1 + max_child;
}
}
node_depth[0]
}
#[must_use]
pub fn eval<F: FunctionSet>(&self, alphabet: &Alphabet<F>, inputs: &[f32]) -> f32 {
let n = self.nodes.len();
if n == 0 {
return 0.0;
}
let mut results = vec![0.0f32; n];
let max_arity = alphabet.max_arity().max(1);
let mut arg_buf = vec![0.0f32; max_arity];
for i in (0..n).rev() {
let symbol = self.nodes[i];
results[i] = match alphabet.classify(symbol) {
SymbolKind::Variable { input_index } => {
inputs.get(input_index).copied().unwrap_or(0.0)
}
SymbolKind::Constant { value } => value,
SymbolKind::Function { .. } => {
let arity = self.arities[i];
let start = self.child_start[i];
let end = (start + arity).min(results.len());
let avail = end.saturating_sub(start);
arg_buf[..avail].copy_from_slice(&results[start..end]);
arg_buf[avail..arity].fill(0.0);
let v = alphabet.functions.apply(symbol, &arg_buf[..arity]);
finite_or_clamp(v)
}
};
}
results[0]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::gep::GepDecoder;
use crate::algorithms::gep::decode::GenotypePhenotypeMap;
use crate::function_set::ArithmeticFunctionSet;
fn alphabet(n_vars: usize) -> Alphabet<ArithmeticFunctionSet> {
Alphabet::new(ArithmeticFunctionSet, n_vars, vec![])
}
#[test]
fn evaluates_x_plus_one() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(0),
Symbol::from_raw(8),
Symbol::from_raw(7),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 3);
approx::assert_relative_eq!(tree.eval(&a, &[2.0]), 3.0, epsilon = 1e-6);
approx::assert_relative_eq!(tree.eval(&a, &[-5.0]), -4.0, epsilon = 1e-6);
}
#[test]
fn evaluates_x_squared_with_depth_one() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(2),
Symbol::from_raw(8),
Symbol::from_raw(8),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
assert_eq!(tree.node_count(), 3);
assert_eq!(tree.depth(), 1);
approx::assert_relative_eq!(tree.eval(&a, &[3.0]), 9.0, epsilon = 1e-6);
}
#[test]
fn single_terminal_is_leaf() {
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);
assert_eq!(tree.depth(), 0);
approx::assert_relative_eq!(tree.eval(&a, &[7.0]), 7.0, epsilon = 1e-6);
}
#[test]
fn test_finite_or_clamp_zeroes_nan_and_clamps_inf() {
approx::assert_relative_eq!(finite_or_clamp(f32::NAN), 0.0, epsilon = 1e-6);
approx::assert_relative_eq!(finite_or_clamp(f32::INFINITY), EVAL_CLAMP);
approx::assert_relative_eq!(finite_or_clamp(f32::NEG_INFINITY), -EVAL_CLAMP);
approx::assert_relative_eq!(finite_or_clamp(3.5), 3.5, epsilon = 1e-6);
}
#[test]
fn test_eval_clamps_overflow_instead_of_zeroing() {
let a = alphabet(1);
let genome = vec![
Symbol::from_raw(2),
Symbol::from_raw(8),
Symbol::from_raw(8),
Symbol::from_raw(8),
];
let tree = GepDecoder.decode(&a, &genome);
let pred = tree.eval(&a, &[1e30]);
approx::assert_relative_eq!(pred, EVAL_CLAMP);
assert!(
(pred - 0.1).powi(2) > 1e20,
"diverged prediction must yield a large error, got pred = {pred}"
);
}
#[test]
fn eval_of_empty_tree_is_zero() {
let a = alphabet(1);
let tree = ExpressionTree::from_parts(Vec::new(), Vec::new(), Vec::new());
assert_eq!(tree.node_count(), 0);
approx::assert_relative_eq!(tree.eval(&a, &[42.0]), 0.0, epsilon = 1e-6);
}
#[test]
fn eval_variable_index_and_inf_policy() {
let a = alphabet(2);
let tree = GepDecoder.decode(&a, &[Symbol::from_raw(9)]);
assert_eq!(tree.node_count(), 1);
approx::assert_relative_eq!(tree.eval(&a, &[]), 0.0, epsilon = 1e-6);
approx::assert_relative_eq!(tree.eval(&a, &[3.0, 7.0]), 7.0, epsilon = 1e-6);
assert!(tree.eval(&a, &[0.0, f32::INFINITY]).is_infinite());
}
#[test]
fn eval_does_not_panic_on_out_of_bounds_child_range() {
let a = alphabet(1);
let tree = ExpressionTree::from_parts(vec![Symbol::from_raw(0)], vec![2], vec![1]);
assert_eq!(tree.node_count(), 1);
let y = tree.eval(&a, &[5.0]);
assert!(y.is_finite());
approx::assert_relative_eq!(y, 0.0, epsilon = 1e-6);
}
#[test]
fn eval_partial_out_of_bounds_child_range() {
let a = alphabet(1);
let tree = ExpressionTree::from_parts(
vec![Symbol::from_raw(0), Symbol::from_raw(8)],
vec![2, 0],
vec![1, 2],
);
let y = tree.eval(&a, &[4.0]);
assert!(y.is_finite());
approx::assert_relative_eq!(y, 4.0, epsilon = 1e-6);
}
}