use yscv_tensor::Tensor;
use crate::Graph;
#[test]
fn backward_relu_masks_negative_inputs() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![4], vec![-1.0, 2.0, -3.0, 4.0]).unwrap());
let y = graph.relu(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
assert_eq!(x_grad.data(), &[0.0, 1.0, 0.0, 1.0]);
}
#[test]
fn backward_exp_multiplies_by_exp_output() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![2], vec![0.0, 1.0]).unwrap());
let y = graph.exp(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
assert!((x_grad.data()[0] - 1.0).abs() < 1e-5); assert!((x_grad.data()[1] - std::f32::consts::E).abs() < 1e-4);
}
#[test]
fn backward_log_divides_by_input() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![2], vec![1.0, 2.0]).unwrap());
let y = graph.log(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
assert!((x_grad.data()[0] - 1.0).abs() < 1e-5);
assert!((x_grad.data()[1] - 0.5).abs() < 1e-5);
}
#[test]
fn backward_sqrt_computes_half_reciprocal_sqrt() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![2], vec![4.0, 9.0]).unwrap());
let y = graph.sqrt(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
assert!((x_grad.data()[0] - 0.25).abs() < 1e-5); assert!((x_grad.data()[1] - 1.0 / 6.0).abs() < 1e-5); }
#[test]
fn backward_sigmoid_computes_s_times_1_minus_s() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![2], vec![0.0, 2.0]).unwrap());
let y = graph.sigmoid(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let y_val = graph.value(y).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
for i in 0..2 {
let s = y_val.data()[i];
let expected = s * (1.0 - s);
assert!(
(x_grad.data()[i] - expected).abs() < 1e-5,
"sigmoid grad mismatch at {i}"
);
}
}
#[test]
fn backward_tanh_computes_1_minus_tanh_sq() {
let mut graph = Graph::new();
let x = graph.variable(Tensor::from_vec(vec![2], vec![0.0, 1.0]).unwrap());
let y = graph.tanh(x).unwrap();
let loss = graph.sum(y).unwrap();
graph.backward(loss).unwrap();
let y_val = graph.value(y).unwrap();
let x_grad = graph.grad(x).unwrap().unwrap();
for i in 0..2 {
let t = y_val.data()[i];
let expected = 1.0 - t * t;
assert!(
(x_grad.data()[i] - expected).abs() < 1e-5,
"tanh grad mismatch at {i}"
);
}
}
#[test]
fn leaky_relu_forward_and_backward() {
let mut g = Graph::new();
let x = g.variable(Tensor::from_vec(vec![3], vec![-2.0, 0.0, 3.0]).unwrap());
let y = g.leaky_relu(x, 0.1).unwrap();
let loss = g.sum(y).unwrap();
g.backward(loss).unwrap();
assert!((g.value(y).unwrap().data()[0] - (-0.2)).abs() < 1e-6);
assert!((g.value(y).unwrap().data()[2] - 3.0).abs() < 1e-6);
let grad = g.grad(x).unwrap().unwrap();
assert!((grad.data()[0] - 0.1).abs() < 1e-6);
assert!((grad.data()[2] - 1.0).abs() < 1e-6);
}
#[test]
fn gelu_forward_and_backward() {
let mut g = Graph::new();
let x = g.variable(Tensor::from_vec(vec![3], vec![-1.0, 0.0, 1.0]).unwrap());
let y = g.gelu(x).unwrap();
let loss = g.sum(y).unwrap();
g.backward(loss).unwrap();
let y_val = g.value(y).unwrap();
assert!((y_val.data()[1] - 0.0).abs() < 1e-6);
let grad = g.grad(x).unwrap().unwrap();
let eps = 1e-4_f32;
for i in 0..3 {
let xi = [-1.0_f32, 0.0, 1.0][i];
let gelu_plus = {
let xp = xi + eps;
xp * (1.0 / (1.0 + (-1.702 * xp).exp()))
};
let gelu_minus = {
let xm = xi - eps;
xm * (1.0 / (1.0 + (-1.702 * xm).exp()))
};
let numerical = (gelu_plus - gelu_minus) / (2.0 * eps);
assert!(
(grad.data()[i] - numerical).abs() < 1e-3,
"gelu grad mismatch at {i}: got {} expected {}",
grad.data()[i],
numerical
);
}
}
#[test]
fn silu_forward_and_backward() {
let mut g = Graph::new();
let x = g.variable(Tensor::from_vec(vec![3], vec![-1.0, 0.0, 1.0]).unwrap());
let y = g.silu(x).unwrap();
let loss = g.sum(y).unwrap();
g.backward(loss).unwrap();
let y_val = g.value(y).unwrap();
assert!((y_val.data()[1] - 0.0).abs() < 1e-6);
let grad = g.grad(x).unwrap().unwrap();
for i in 0..3 {
let xi = [-1.0_f32, 0.0, 1.0][i];
let s = 1.0 / (1.0 + (-xi).exp());
let expected = s + xi * s * (1.0 - s);
assert!(
(grad.data()[i] - expected).abs() < 1e-5,
"silu grad mismatch at {i}: got {} expected {}",
grad.data()[i],
expected
);
}
}
#[test]
fn mish_forward_and_backward() {
let mut g = Graph::new();
let x = g.variable(Tensor::from_vec(vec![3], vec![-1.0, 0.0, 1.0]).unwrap());
let y = g.mish(x).unwrap();
let loss = g.sum(y).unwrap();
g.backward(loss).unwrap();
let y_val = g.value(y).unwrap();
assert!((y_val.data()[1] - 0.0).abs() < 1e-6);
let grad = g.grad(x).unwrap().unwrap();
let eps = 1e-4_f32;
for i in 0..3 {
let xi = [-1.0_f32, 0.0, 1.0][i];
let mish_val = |x: f32| -> f32 {
let sp = (1.0 + x.exp()).ln();
x * sp.tanh()
};
let numerical = (mish_val(xi + eps) - mish_val(xi - eps)) / (2.0 * eps);
assert!(
(grad.data()[i] - numerical).abs() < 1e-3,
"mish grad mismatch at {i}: got {} expected {}",
grad.data()[i],
numerical
);
}
}
#[test]
fn softmax_forward_and_backward() {
let mut g = Graph::new();
let x = g.variable(Tensor::from_vec(vec![1, 3], vec![1.0, 2.0, 3.0]).unwrap());
let sm = g.softmax(x).unwrap();
let loss = g.sum(sm).unwrap();
g.backward(loss).unwrap();
let sm_val = g.value(sm).unwrap();
let sum: f32 = sm_val.data().iter().sum();
assert!((sum - 1.0).abs() < 1e-5);
let grad = g.grad(x).unwrap().unwrap();
assert_eq!(grad.shape(), &[1, 3]);
}