pub mod continuous;
pub mod non_bias;
pub mod simple;
pub use continuous::Continuous;
pub use non_bias::NonBias;
pub use simple::Simple;
use crate::{Connection, Genome};
pub mod activate {
use core::f64::consts::E;
pub fn steep_sigmoid(x: f64) -> f64 {
1. / (1. + E.powf(-4.9 * x))
}
pub fn relu(x: f64) -> f64 {
if x < 0. {
0.
} else {
x
}
}
}
pub mod loss {
pub fn decay_quadratic(want: f64, x: f64) -> f64 {
1. - (want - x).abs().powf(2.)
}
pub fn decay_linear(want: f64, have: f64) -> f64 {
if have.is_nan() {
f64::MIN
} else {
want - (want - have).abs()
}
}
}
pub trait Network {
fn step<F: Fn(f64) -> f64>(&mut self, prec: usize, input: &[f64], σ: F);
fn flush(&mut self);
fn output(&self) -> &[f64];
}
pub trait Recurrent: Network {}
pub trait Linear: Network {}
pub trait Stateful: Network {}
pub trait Stateless: Network {}
pub trait FromGenome<C: Connection, G: Genome<C>>: Network {
fn from_genome(genome: &G) -> Self;
}
pub trait ToNetwork<NN: Network, C: Connection>: Genome<C> {
fn network(&self) -> NN;
}
impl<NN: Network, C: Connection, G: Genome<C>> ToNetwork<NN, C> for G
where
NN: FromGenome<C, G>,
{
fn network(&self) -> NN {
NN::from_genome(self)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::genome::{self, connection::BWConnection, WConnection};
use eevee_macros::fn_matrix;
fn_matrix! {
C: WConnection | BWConnection,
G: genome::Recurrent<C>,
NN: Continuous | NonBias,
#[test]
fn test_from_genome_output_bounds() {
let (genome, _) = G::new(3, 2);
let nn = NN::from_genome(&genome);
assert_eq!(nn.output().len(), genome.action().len());
}
#[test]
fn test_from_genome_empty_action() {
let (genome, _) = G::new(3, 0);
let nn = NN::from_genome(&genome);
assert_eq!(nn.output().len(), genome.action().len());
}
#[test]
fn test_step_with_correct_input_size() {
let (genome, _) = G::new(3, 2);
let mut nn = NN::from_genome(&genome);
let input: Vec<_> = (0..genome.sensory().len()).map(|i| i as f64).collect();
nn.step(1, &input, |x| x);
assert_eq!(nn.output().len(), genome.action().len());
}
#[test]
fn test_flush_resets_state() {
let (genome, _) = G::new(2, 2);
let mut nn = NN::from_genome(&genome);
let input = vec![1.0, 0.5];
nn.step(3, &input, |x| x.signum());
let output_before_flush = nn.output().to_vec();
nn.flush();
nn.step(1, &input, |x| x.signum());
let output_after_flush = nn.output().to_vec();
if !genome.connections().iter().any(|c| c.enabled()) {
assert_eq!(output_before_flush, output_after_flush);
} else {
let magnitude_before: f64 = output_before_flush.iter().map(|x| x.abs()).sum();
let magnitude_after: f64 = output_after_flush.iter().map(|x| x.abs()).sum();
assert_ne!(magnitude_before, magnitude_after);
}
}
#[test]
fn test_different_activations_differ() {
let (genome, _) = G::new(2, 2);
let mut nn1 = NN::from_genome(&genome);
let mut nn2 = NN::from_genome(&genome);
let input = vec![0.5, -0.5];
nn1.step(3, &input, |x| x);
nn2.step(3, &input, |x| x.abs());
if genome.connections().iter().any(|c| c.enabled()) {
let magnitude1: f64 = nn1.output().iter().map(|x| x.abs()).sum();
let magnitude2: f64 = nn2.output().iter().map(|x| x.abs()).sum();
assert_ne!(magnitude1, magnitude2);
}
}
#[test]
fn test_prec_affects_convergence() {
let (genome, _) = G::new(2, 2);
let mut nn1 = NN::from_genome(&genome);
let mut nn2 = NN::from_genome(&genome);
let input = vec![1.0, 0.5];
nn1.step(1, &input, |x| x.tanh());
nn2.step(5, &input, |x| x.tanh());
if genome.connections().iter().any(|c| c.enabled()) {
assert_ne!(nn1.output(), nn2.output());
}
}
}
}