#![cfg(all(feature = "grad", feature = "eval"))]
use rlx_tensor::Tensor;
fn approx(a: &[f32], b: &[f32]) {
assert_eq!(a.len(), b.len(), "length mismatch: {a:?} vs {b:?}");
for (x, y) in a.iter().zip(b) {
assert!((x - y).abs() < 1e-4, "{a:?} != {b:?}");
}
}
#[test]
fn grad_of_product_sum() {
let a = Tensor::from_vec(vec![1.0, 2.0, 3.0], [3]);
let b = Tensor::from_vec(vec![4.0, 5.0, 6.0], [3]);
let loss = (&a * &b).sum([0], false);
let g = loss.grad(&[&a, &b]);
approx(&g[0].to_vec(), &[4.0, 5.0, 6.0]); approx(&g[1].to_vec(), &[1.0, 2.0, 3.0]); }
#[test]
fn grad_accumulates_for_reused_input() {
let x = Tensor::from_vec(vec![1.0, 2.0, 3.0], [3]);
let loss = (&x * &x).sum([0], false);
let g = loss.grad(&[&x]);
approx(&g[0].to_vec(), &[2.0, 4.0, 6.0]);
}
#[test]
fn grad_through_relu() {
let x = Tensor::from_vec(vec![-1.0, 2.0, -3.0, 4.0], [4]);
let loss = x.relu().sum([0], false);
let g = loss.grad(&[&x]);
approx(&g[0].to_vec(), &[0.0, 1.0, 0.0, 1.0]);
}
#[test]
fn grad_of_scaled_sum() {
let x = Tensor::from_vec(vec![1.0, 2.0, 3.0], [3]);
let loss = (&x * 3.0f32).sum([0], false);
let g = loss.grad(&[&x]);
approx(&g[0].to_vec(), &[3.0, 3.0, 3.0]);
}
#[test]
fn grad_linear_layer() {
let x = Tensor::from_vec(vec![1.0, 2.0], [1, 2]);
let w = Tensor::from_vec(vec![1.0, 0.0, 0.0, 1.0], [2, 2]);
let loss = x.matmul(&w).sum([0, 1], false);
let g = loss.grad(&[&w]);
approx(&g[0].to_vec(), &[1.0, 1.0, 2.0, 2.0]);
}