use ndarray::Array2;
#[inline]
fn apply_sigmoid(arr: Array2<f32>) -> Array2<f32> {
arr.mapv(|x| 1.0 / (1.0 + (-x).exp()))
}
#[inline]
fn input_step(step: usize, timesteps: usize, go_backwards: bool) -> usize {
if go_backwards {
timesteps - 1 - step
} else {
step
}
}
mod cell;
pub mod gate;
pub mod gru;
pub mod lstm;
mod rnn;
pub mod simple_rnn;
mod validation;
pub use gru::GRU;
pub use lstm::LSTM;
pub use simple_rnn::SimpleRNN;
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use ndarray::array;
#[test]
fn input_step_forward_is_the_identity() {
for step in 0..5 {
assert_eq!(input_step(step, 5, false), step);
}
}
#[test]
fn input_step_backward_reverses_the_time_axis() {
let got: Vec<usize> = (0..5).map(|step| input_step(step, 5, true)).collect();
assert_eq!(got, vec![4, 3, 2, 1, 0]);
}
#[test]
fn apply_sigmoid_zero_gives_half() {
let input = array![[0.0_f32]];
let output = apply_sigmoid(input);
assert_abs_diff_eq!(output[[0, 0]], 0.5_f32, epsilon = 1e-6);
}
#[test]
fn apply_sigmoid_large_positive_approaches_one() {
let input = array![[500.0_f32]];
let output = apply_sigmoid(input);
assert_abs_diff_eq!(output[[0, 0]], 1.0_f32, epsilon = 1e-6);
}
#[test]
fn apply_sigmoid_large_positive_inputs_saturate_equally() {
let out_500 = apply_sigmoid(array![[500.0_f32]]);
let out_1000 = apply_sigmoid(array![[1000.0_f32]]);
assert_abs_diff_eq!(out_500[[0, 0]], out_1000[[0, 0]], epsilon = 1e-9);
assert_abs_diff_eq!(out_1000[[0, 0]], 1.0_f32, epsilon = 1e-6);
}
#[test]
fn apply_sigmoid_large_negative_approaches_zero() {
let input = array![[-1000.0_f32]];
let output = apply_sigmoid(input);
assert_abs_diff_eq!(output[[0, 0]], 0.0_f32, epsilon = 1e-6);
}
#[test]
fn apply_sigmoid_no_nan_or_inf() {
let input = array![[
f32::NEG_INFINITY,
-1e10,
-1.0,
0.0,
1.0,
1e10,
f32::INFINITY
]];
let output = apply_sigmoid(input);
for &v in output.iter() {
assert!(
v.is_finite(),
"apply_sigmoid produced non-finite value: {v}"
);
}
}
}