use super::node::{GpNode, Node};
use rand::Rng;
use std::fmt;
#[derive(Clone, Debug)]
pub enum MathNode {
Add,
Sub,
Mul,
ProtectedDiv,
Const(f64),
Var(usize),
}
impl fmt::Display for MathNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MathNode::Add => write!(f, "add"),
MathNode::Sub => write!(f, "sub"),
MathNode::Mul => write!(f, "mul"),
MathNode::ProtectedDiv => write!(f, "pdiv"),
MathNode::Const(v) => write!(f, "{:.4}", v),
MathNode::Var(i) => write!(f, "x{}", i),
}
}
}
impl GpNode for MathNode {
fn arity(&self) -> usize {
match self {
MathNode::Add | MathNode::Sub | MathNode::Mul | MathNode::ProtectedDiv => 2,
MathNode::Const(_) | MathNode::Var(_) => 0,
}
}
fn evaluate(&self, args: &[f64]) -> f64 {
match self {
MathNode::Add => args[0] + args[1],
MathNode::Sub => args[0] - args[1],
MathNode::Mul => args[0] * args[1],
MathNode::ProtectedDiv => {
if args[1].abs() < 1e-10 {
1.0
} else {
args[0] / args[1]
}
}
MathNode::Const(v) => *v,
MathNode::Var(_) => 0.0,
}
}
fn is_terminal(&self) -> bool {
matches!(self, MathNode::Const(_) | MathNode::Var(_))
}
fn sample_random_terminal(rng: &mut impl Rng) -> Self {
MathNode::Const(rng.random_range(-1.0_f64..=1.0_f64))
}
fn all_functions() -> Vec<Self> {
vec![
MathNode::Add,
MathNode::Sub,
MathNode::Mul,
MathNode::ProtectedDiv,
]
}
}
impl Default for MathNode {
fn default() -> Self {
MathNode::Const(0.0)
}
}
#[derive(Clone, Debug, Default)]
pub enum BoolNode {
#[default]
And,
Or,
Not,
Gt,
Lt,
}
impl fmt::Display for BoolNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BoolNode::And => write!(f, "and"),
BoolNode::Or => write!(f, "or"),
BoolNode::Not => write!(f, "not"),
BoolNode::Gt => write!(f, "gt"),
BoolNode::Lt => write!(f, "lt"),
}
}
}
impl GpNode for BoolNode {
fn arity(&self) -> usize {
match self {
BoolNode::And | BoolNode::Or | BoolNode::Gt | BoolNode::Lt => 2,
BoolNode::Not => 1,
}
}
fn evaluate(&self, args: &[f64]) -> f64 {
match self {
BoolNode::And => {
if args[0] != 0.0 && args[1] != 0.0 {
1.0
} else {
0.0
}
}
BoolNode::Or => {
if args[0] != 0.0 || args[1] != 0.0 {
1.0
} else {
0.0
}
}
BoolNode::Not => {
if args[0] == 0.0 {
1.0
} else {
0.0
}
}
BoolNode::Gt => {
if args[0] > args[1] {
1.0
} else {
0.0
}
}
BoolNode::Lt => {
if args[0] < args[1] {
1.0
} else {
0.0
}
}
}
}
fn is_terminal(&self) -> bool {
false
}
fn sample_random_terminal(_rng: &mut impl Rng) -> Self {
unreachable!(
"BoolNode has no terminals; do not use BoolNode as a standalone GpNode — \
combine with MathNode or add terminal variants"
)
}
fn all_functions() -> Vec<Self> {
vec![
BoolNode::And,
BoolNode::Or,
BoolNode::Not,
BoolNode::Gt,
BoolNode::Lt,
]
}
}
impl Node<MathNode> {
pub fn eval_with_vars(&self, vars: &[f64]) -> f64 {
match self {
Node::Terminal(MathNode::Var(i)) => vars.get(*i).copied().unwrap_or(0.0),
Node::Terminal(other) => other.evaluate(&[]),
Node::Function { value, children } => {
let child_vals: Vec<f64> =
children.iter().map(|c| c.eval_with_vars(vars)).collect();
value.evaluate(&child_vals)
}
}
}
}