use ndarray::{Array, Array2};
use ndarray_rand::rand::rngs::StdRng;
use ndarray_rand::{RandomExt, rand_distr::Uniform};
#[inline]
fn apply_sigmoid(arr: Array2<f32>) -> Array2<f32> {
arr.mapv(|x| 1.0 / (1.0 + (-x).exp()))
}
fn orthogonal_init(size: usize, rng: &mut StdRng) -> Array2<f32> {
let mut matrix = Array::random_using((size, size), Uniform::new(-1.0, 1.0).unwrap(), rng);
const EPSILON: f32 = 1e-8;
for i in 0..size {
for j in 0..i {
let mut projection = 0.0;
for k in 0..size {
projection += matrix[[k, i]] * matrix[[k, j]];
}
for k in 0..size {
matrix[[k, i]] -= projection * matrix[[k, j]];
}
}
let mut norm = 0.0f32;
for k in 0..size {
norm += matrix[[k, i]] * matrix[[k, i]];
}
norm = norm.sqrt();
if norm > EPSILON {
for k in 0..size {
matrix[[k, i]] /= norm;
}
} else {
for k in 0..size {
matrix[[k, i]] = if k == i { 1.0 } else { 0.0 };
}
}
}
matrix
}
pub mod gate;
pub mod gru;
pub mod lstm;
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;
use ndarray_rand::rand::SeedableRng;
#[test]
fn orthogonal_init_size3_columns_are_orthonormal() {
let m = orthogonal_init(3, &mut StdRng::seed_from_u64(0));
let mt_m = m.t().dot(&m);
for row in 0..3 {
for col in 0..3 {
let expected = if row == col { 1.0_f32 } else { 0.0_f32 };
assert_abs_diff_eq!(mt_m[[row, col]], expected, epsilon = 1e-5);
}
}
}
#[test]
fn orthogonal_init_size1_abs_is_one() {
let m = orthogonal_init(1, &mut StdRng::seed_from_u64(0));
assert_eq!(m.shape(), &[1, 1]);
assert_abs_diff_eq!(m[[0, 0]].abs(), 1.0_f32, epsilon = 1e-6);
}
#[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}"
);
}
}
}