#![cfg(all(feature = "transforms", feature = "eval"))]
use rlx_tensor::{Func, shape};
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:?}");
}
}
fn sum_sq() -> Func {
Func::new("sum_sq", |s| {
let x = s.input("x", shape![3]);
(&x * &x).sum([0], false)
})
}
#[test]
fn func_runs_forward() {
let out = sum_sq().run(&[("x", &[1.0, 2.0, 3.0])]);
approx(&out[0], &[14.0]); }
#[test]
fn grad_transform() {
let df = sum_sq().grad(&["x"]);
let out = df.run(&[("x", &[1.0, 2.0, 3.0])]);
approx(&out[0], &[2.0, 4.0, 6.0]);
}
#[test]
fn vmap_transform() {
let batched = sum_sq().vmap(&["x"], 2);
let out = batched.run(&[("x", &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0])]);
approx(&out[0], &[14.0, 77.0]); }
#[test]
fn vmap_of_grad_composes() {
let batched_grad = sum_sq().grad(&["x"]).vmap(&["x"], 2);
let out = batched_grad.run(&[("x", &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0])]);
approx(&out[0], &[2.0, 4.0, 6.0, 8.0, 10.0, 12.0]);
}
#[test]
fn param_binding_forward() {
let f = Func::new("lin", |s| {
let x = s.input("x", shape![3]);
let w = s.param("w", shape![3]);
(&x * &w).sum([0], false)
})
.with_param("w", vec![2.0, 2.0, 2.0]);
approx(&f.run(&[("x", &[1.0, 2.0, 3.0])])[0], &[12.0]);
}
#[test]
fn grad_wrt_param_uses_bound_value() {
let f = Func::new("sqw", |s| {
let w = s.param("w", shape![3]);
(&w * &w).sum([0], false)
})
.with_param("w", vec![1.0, 2.0, 3.0]);
let g = f.grad(&["w"]);
approx(&g.run(&[])[0], &[2.0, 4.0, 6.0]);
}
#[test]
fn jit_composes_and_runs() {
let f = sum_sq().grad(&["x"]).jit();
approx(&f.run(&[("x", &[1.0, 2.0, 3.0])])[0], &[2.0, 4.0, 6.0]);
approx(&f.run(&[("x", &[5.0, 6.0, 7.0])])[0], &[10.0, 12.0, 14.0]);
let g = f.clone();
approx(&g.run(&[("x", &[0.0, 1.0, 2.0])])[0], &[0.0, 2.0, 4.0]);
}