use approx::assert_relative_eq;
use numrs2::blas;
use numrs2::linalg_accelerated::AcceleratedBlas;
use numrs2::prelude::*;
const EPS: f64 = 1e-10;
fn a_log() -> Array<f64> {
Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).reshape(&[2, 3])
}
fn a_stored_t() -> Array<f64> {
Array::from_vec(vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]).reshape(&[3, 2])
}
fn b_log() -> Array<f64> {
Array::from_vec(vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0]).reshape(&[3, 2])
}
fn b_stored_t() -> Array<f64> {
Array::from_vec(vec![7.0, 9.0, 11.0, 8.0, 10.0, 12.0]).reshape(&[2, 3])
}
const AB_EXPECTED: [[f64; 2]; 2] = [[58.0, 64.0], [139.0, 154.0]];
fn assert_2x2_eq(actual: &Array<f64>, expected: &[[f64; 2]; 2], msg: &str) {
assert_eq!(actual.shape(), vec![2, 2], "{msg}: unexpected shape");
for i in 0..2 {
for j in 0..2 {
let got = actual.get(&[i, j]).expect("in-bounds index");
assert_relative_eq!(got, expected[i][j], epsilon = EPS);
}
}
}
#[test]
fn test_gemm_no_transpose_matches_hand_computed_product() {
let a = a_log();
let b = b_log();
let mut c = Array::<f64>::zeros(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c, 1.0, 0.0, false, false)
.expect("gemm(false, false) should succeed");
assert_2x2_eq(&c, &AB_EXPECTED, "trans_a=false, trans_b=false");
}
#[test]
fn test_gemm_transpose_a_only_matches_hand_computed_product() {
let a = a_stored_t(); let b = b_log(); let mut c = Array::<f64>::zeros(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c, 1.0, 0.0, true, false)
.expect("gemm(true, false) should succeed");
assert_2x2_eq(&c, &AB_EXPECTED, "trans_a=true, trans_b=false");
}
#[test]
fn test_gemm_transpose_b_only_matches_hand_computed_product() {
let a = a_log(); let b = b_stored_t(); let mut c = Array::<f64>::zeros(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c, 1.0, 0.0, false, true)
.expect("gemm(false, true) should succeed");
assert_2x2_eq(&c, &AB_EXPECTED, "trans_a=false, trans_b=true");
}
#[test]
fn test_gemm_transpose_both_matches_hand_computed_product() {
let a = a_stored_t(); let b = b_stored_t(); let mut c = Array::<f64>::zeros(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c, 1.0, 0.0, true, true)
.expect("gemm(true, true) should succeed");
assert_2x2_eq(&c, &AB_EXPECTED, "trans_a=true, trans_b=true");
}
#[test]
fn test_gemm_transpose_a_with_beta_and_prefilled_c() {
let a = a_stored_t();
let b = b_log();
let mut c = Array::from_vec(vec![1.0, 1.0, 1.0, 1.0]).reshape(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c, 2.0, 3.0, true, false)
.expect("gemm(true, false) with beta should succeed");
assert_2x2_eq(
&c,
&[[119.0, 131.0], [281.0, 311.0]],
"trans_a=true, trans_b=false, alpha=2, beta=3",
);
}
#[test]
fn test_gemm_transpose_all_combos_match_naive_blas_reference() {
let combos: [(bool, bool); 4] = [(false, false), (true, false), (false, true), (true, true)];
for (trans_a, trans_b) in combos {
let a = if trans_a { a_stored_t() } else { a_log() };
let b = if trans_b { b_stored_t() } else { b_log() };
let mut c_acc = Array::<f64>::zeros(&[2, 2]);
AcceleratedBlas::gemm(&a, &b, &mut c_acc, 1.0, 0.0, trans_a, trans_b)
.unwrap_or_else(|e| panic!("AcceleratedBlas::gemm({trans_a}, {trans_b}) failed: {e}"));
let mut c_ref = Array::<f64>::zeros(&[2, 2]);
blas::gemm(&a, &b, &mut c_ref, 1.0, 0.0, trans_a, trans_b)
.unwrap_or_else(|e| panic!("blas::gemm({trans_a}, {trans_b}) failed: {e}"));
for (i, row) in AB_EXPECTED.iter().enumerate() {
for (j, expected) in row.iter().enumerate() {
let acc_val = c_acc.get(&[i, j]).expect("in-bounds index");
let ref_val = c_ref.get(&[i, j]).expect("in-bounds index");
assert_relative_eq!(acc_val, ref_val, epsilon = EPS);
assert_relative_eq!(acc_val, *expected, epsilon = EPS);
}
}
}
}
#[test]
fn test_gemv_no_transpose_matches_hand_computed_product() {
let a = a_log();
let x = Array::from_vec(vec![2.0, 1.0, 3.0]);
let mut y = Array::<f64>::zeros(&[2]);
AcceleratedBlas::gemv(&a, &x, &mut y, 1.0, 0.0, false).expect("gemv(false) should succeed");
assert_relative_eq!(y.get(&[0]).expect("index"), 13.0, epsilon = EPS);
assert_relative_eq!(y.get(&[1]).expect("index"), 31.0, epsilon = EPS);
}
#[test]
fn test_gemv_transpose_matches_hand_computed_product() {
let a = a_log();
let x = Array::from_vec(vec![2.0, 3.0]);
let mut y = Array::<f64>::zeros(&[3]);
AcceleratedBlas::gemv(&a, &x, &mut y, 1.0, 0.0, true).expect("gemv(true) should succeed");
assert_relative_eq!(y.get(&[0]).expect("index"), 14.0, epsilon = EPS);
assert_relative_eq!(y.get(&[1]).expect("index"), 19.0, epsilon = EPS);
assert_relative_eq!(y.get(&[2]).expect("index"), 24.0, epsilon = EPS);
}
#[test]
fn test_gemv_no_transpose_with_beta_and_prefilled_y() {
let a = a_log();
let x = Array::from_vec(vec![2.0, 1.0, 3.0]);
let mut y = Array::from_vec(vec![10.0, 20.0]);
AcceleratedBlas::gemv(&a, &x, &mut y, 2.0, -1.0, false)
.expect("gemv(false) with beta should succeed");
assert_relative_eq!(y.get(&[0]).expect("index"), 16.0, epsilon = EPS);
assert_relative_eq!(y.get(&[1]).expect("index"), 42.0, epsilon = EPS);
}
#[test]
fn test_gemv_transpose_with_beta_and_prefilled_y() {
let a = a_log();
let x = Array::from_vec(vec![2.0, 3.0]);
let mut y = Array::from_vec(vec![1.0, 2.0, 3.0]);
AcceleratedBlas::gemv(&a, &x, &mut y, 0.5, 2.0, true)
.expect("gemv(true) with beta should succeed");
assert_relative_eq!(y.get(&[0]).expect("index"), 9.0, epsilon = EPS);
assert_relative_eq!(y.get(&[1]).expect("index"), 13.5, epsilon = EPS);
assert_relative_eq!(y.get(&[2]).expect("index"), 18.0, epsilon = EPS);
}
#[test]
fn test_gemv_3x2_physical_matrix_both_transpose_flags() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).reshape(&[3, 2]);
let x_false = Array::from_vec(vec![1.0, 2.0]);
let mut y_false = Array::<f64>::zeros(&[3]);
AcceleratedBlas::gemv(&a, &x_false, &mut y_false, 1.0, 0.0, false)
.expect("gemv(false) on 3x2 matrix should succeed");
assert_relative_eq!(y_false.get(&[0]).expect("index"), 5.0, epsilon = EPS);
assert_relative_eq!(y_false.get(&[1]).expect("index"), 11.0, epsilon = EPS);
assert_relative_eq!(y_false.get(&[2]).expect("index"), 17.0, epsilon = EPS);
let x_true = Array::from_vec(vec![1.0, 2.0, 3.0]);
let mut y_true = Array::<f64>::zeros(&[2]);
AcceleratedBlas::gemv(&a, &x_true, &mut y_true, 1.0, 0.0, true)
.expect("gemv(true) on 3x2 matrix should succeed");
assert_relative_eq!(y_true.get(&[0]).expect("index"), 22.0, epsilon = EPS);
assert_relative_eq!(y_true.get(&[1]).expect("index"), 28.0, epsilon = EPS);
}
#[test]
fn test_gemv_both_transpose_flags_match_naive_blas_reference() {
let a = a_log();
for trans in [false, true] {
let (x, y_len) = if trans {
(Array::from_vec(vec![2.0, 3.0]), 3)
} else {
(Array::from_vec(vec![2.0, 1.0, 3.0]), 2)
};
let mut y_acc = Array::<f64>::zeros(&[y_len]);
AcceleratedBlas::gemv(&a, &x, &mut y_acc, 1.0, 0.0, trans)
.unwrap_or_else(|e| panic!("AcceleratedBlas::gemv(trans={trans}) failed: {e}"));
let mut y_ref = Array::<f64>::zeros(&[y_len]);
blas::gemv(&a, &x, &mut y_ref, 1.0, 0.0, trans)
.unwrap_or_else(|e| panic!("blas::gemv(trans={trans}) failed: {e}"));
for i in 0..y_len {
let acc_val = y_acc.get(&[i]).expect("in-bounds index");
let ref_val = y_ref.get(&[i]).expect("in-bounds index");
assert_relative_eq!(acc_val, ref_val, epsilon = EPS);
}
}
}