#![cfg(feature = "math")]
use vyre_primitives::math::symmetric_eigen_jacobi::EIGENVECTOR_SIGN_EPSILON;
use vyre_primitives::math::tensor_train_decompose::tensor_train_decompose_step;
use vyre_primitives::wire::{decode_f32_le_bytes_all as unpack_f32, pack_f32_slice as pack_f32};
use vyre_reference::value::Value;
fn xorshift(state: &mut u32) -> u32 {
*state ^= *state << 13;
*state ^= *state >> 17;
*state ^= *state << 5;
*state
}
fn rand_f32(state: &mut u32) -> f32 {
let bits = xorshift(state);
((bits >> 8) as f32 / (1u32 << 24) as f32) * 4.0 - 2.0
}
struct StepOutputs {
u: Vec<f32>,
rem: Vec<f32>,
rotated: Vec<f32>,
evec: Vec<f32>,
}
fn run(matrix: &[f32], m: u32, n: u32, r_next: u32) -> StepOutputs {
let program = tensor_train_decompose_step("input", "u", "rem", 1, m, n, r_next);
let gram = (n * n) as usize;
let outputs = vyre_reference::reference_eval(
&program,
&[
Value::from(pack_f32(matrix)),
Value::from(pack_f32(&vec![0.0f32; (m * r_next) as usize])),
Value::from(pack_f32(&vec![0.0f32; (r_next * n) as usize])),
Value::from(pack_f32(&vec![0.0f32; gram])),
Value::from(pack_f32(&vec![0.0f32; gram])),
Value::from(pack_f32(&vec![0.0f32; n as usize])),
],
)
.expect("tensor_train_decompose_step reference evaluation must succeed");
let read = |name: &str| {
unpack_f32(
&outputs[vyre_reference::output_index(&program, name)
.unwrap_or_else(|| panic!("{name} must be a program output"))]
.to_bytes(),
)
};
StepOutputs {
u: read("u"),
rem: read("rem"),
rotated: read("tt_ata"),
evec: read("tt_evec"),
}
}
fn gram(matrix: &[f32], m: usize, n: usize) -> Vec<f64> {
let mut g = vec![0.0f64; n * n];
for row in 0..m {
for col_a in 0..n {
let a = f64::from(matrix[row * n + col_a]);
for col_b in 0..n {
g[col_a * n + col_b] += a * f64::from(matrix[row * n + col_b]);
}
}
}
g
}
fn assert_eigen_contract(g: &[f64], out: &StepOutputs, n: usize, ctx: &str) {
let g_mag = g.iter().map(|x| x * x).sum::<f64>().sqrt().max(1.0);
let resid_tol = 2.0e-2 * g_mag;
for k in 0..n {
let lambda = f64::from(out.rotated[k * n + k]);
let vk: Vec<f64> = (0..n).map(|i| f64::from(out.evec[i * n + k])).collect();
let norm = vk.iter().map(|x| x * x).sum::<f64>().sqrt();
assert!(
(norm - 1.0).abs() <= 1.0e-2,
"{ctx}: eigenvector {k} is not unit-norm (|v|={norm})"
);
for row in 0..n {
let gv: f64 = (0..n).map(|col| g[row * n + col] * vk[col]).sum();
let residual = (gv - lambda * vk[row]).abs();
assert!(
residual <= resid_tol,
"{ctx}: (G·v - λv)[{row}] = {residual} exceeds {resid_tol} for eigenpair {k} \
(λ={lambda}); the Gram eigensolve is wrong"
);
}
let deciding = (0..n).find(|&i| out.evec[i * n + k].abs() > EIGENVECTOR_SIGN_EPSILON);
if let Some(i) = deciding {
assert!(
out.evec[i * n + k] > 0.0,
"{ctx}: eigenvector column {k} is not sign-canonical; first component above \
{EIGENVECTOR_SIGN_EPSILON} is evec[{i}][{k}]={}",
out.evec[i * n + k]
);
}
}
for col_a in 0..n {
for col_b in 0..n {
let dot: f64 = (0..n)
.map(|i| f64::from(out.evec[i * n + col_a]) * f64::from(out.evec[i * n + col_b]))
.sum();
let expected = if col_a == col_b { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() <= 1.0e-2,
"{ctx}: (VᵀV)[{col_a},{col_b}] = {dot}, expected {expected}"
);
}
}
}
fn assert_truncation_contract(
g: &[f64],
out: &StepOutputs,
m: usize,
n: usize,
r_next: usize,
ctx: &str,
) {
let g_mag = g.iter().map(|x| x * x).sum::<f64>().sqrt().max(1.0);
let mut spectrum: Vec<f64> = (0..n).map(|k| f64::from(out.rotated[k * n + k])).collect();
spectrum.sort_by(|left, right| right.partial_cmp(left).expect("eigenvalues are finite"));
let sigmas: Vec<f64> = (0..r_next)
.map(|rank| {
(0..n)
.map(|col| {
let x = f64::from(out.rem[rank * n + col]);
x * x
})
.sum::<f64>()
.sqrt()
})
.collect();
for rank in 0..r_next {
let expected = spectrum[rank].max(0.0).sqrt();
assert!(
(sigmas[rank] - expected).abs() <= 2.0e-2 * g_mag.sqrt(),
"{ctx}: |remainder row {rank}| = {} but the {rank}-th largest eigenvalue gives \
σ = {expected}; the rank selection or the σ scaling is wrong",
sigmas[rank]
);
if rank > 0 {
assert!(
sigmas[rank] <= sigmas[rank - 1] + 1.0e-3 * g_mag.sqrt(),
"{ctx}: singular values are not descending: σ[{}]={}, σ[{rank}]={}",
rank - 1,
sigmas[rank - 1],
sigmas[rank]
);
}
}
for col_a in 0..r_next {
if sigmas[col_a] <= 1.0e-3 {
continue;
}
for col_b in 0..r_next {
if sigmas[col_b] <= 1.0e-3 {
continue;
}
let dot: f64 = (0..m)
.map(|row| {
f64::from(out.u[row * r_next + col_a]) * f64::from(out.u[row * r_next + col_b])
})
.sum();
let expected = if col_a == col_b { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() <= 2.0e-2,
"{ctx}: (UᵀU)[{col_a},{col_b}] = {dot}, expected {expected}; the core columns \
are not left singular vectors"
);
}
}
}
#[test]
fn gram_eigendecomposition_satisfies_the_eigenpair_contract() {
let mut state = 0x51E7_C0DEu32;
let mut spread = 0u32;
for case in 0..80u32 {
let n = 2 + xorshift(&mut state) % 4; let m = n + xorshift(&mut state) % (n + 1); let matrix: Vec<f32> = (0..(m * n)).map(|_| rand_f32(&mut state)).collect();
let out = run(&matrix, m, n, n);
let g = gram(&matrix, m as usize, n as usize);
let ctx = format!("case {case} (m={m}, n={n})");
assert_eigen_contract(&g, &out, n as usize, &ctx);
assert_truncation_contract(&g, &out, m as usize, n as usize, n as usize, &ctx);
let diag: Vec<f64> = (0..n as usize)
.map(|k| f64::from(out.rotated[k * n as usize + k]))
.collect();
let hi = diag.iter().cloned().fold(f64::MIN, f64::max);
let lo = diag.iter().cloned().fold(f64::MAX, f64::min);
if hi - lo > 0.5 {
spread += 1;
}
}
assert!(
spread > 70,
"only {spread}/80 Gram matrices had a spread spectrum, so the rotation path is \
under-exercised"
);
}
#[test]
fn truncated_step_keeps_the_eigen_contract_on_the_retained_ranks() {
let mut state = 0x0BAD_F00Du32;
for case in 0..40u32 {
let n = 3 + xorshift(&mut state) % 3; let m = n + 1 + xorshift(&mut state) % n;
let r_next = 1 + xorshift(&mut state) % (n - 1); let matrix: Vec<f32> = (0..(m * n)).map(|_| rand_f32(&mut state)).collect();
let out = run(&matrix, m, n, r_next);
let g = gram(&matrix, m as usize, n as usize);
let ctx = format!("case {case} (m={m}, n={n}, r_next={r_next})");
assert_eigen_contract(&g, &out, n as usize, &ctx);
assert_truncation_contract(&g, &out, m as usize, n as usize, r_next as usize, &ctx);
}
}
#[test]
fn hand_checked_gram_spectrum_is_reproduced_exactly() {
let matrix = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let out = run(&matrix, 4, 2, 2);
let g = gram(&matrix, 4, 2);
assert_eigen_contract(&g, &out, 2, "hand-checked 4x2");
assert_truncation_contract(&g, &out, 4, 2, 2, "hand-checked 4x2");
let mut spectrum = [
f64::from(out.rotated[0]),
f64::from(out.rotated[3]),
];
spectrum.sort_by(|left, right| right.partial_cmp(left).expect("finite"));
assert!(
(spectrum[0] - 203.607_09).abs() < 1.0e-2,
"dominant Gram eigenvalue must be 203.60709, got {}",
spectrum[0]
);
assert!(
(spectrum[1] - 0.392_911_9).abs() < 1.0e-3,
"trailing Gram eigenvalue must be 0.3929119, got {}",
spectrum[1]
);
let dominant_col = usize::from(f64::from(out.rotated[3]) > f64::from(out.rotated[0]));
let v = [
f64::from(out.evec[dominant_col]),
f64::from(out.evec[2 + dominant_col]),
];
assert!(
(v[0] - 0.641_423).abs() < 1.0e-4 && (v[1] - 0.767_187).abs() < 1.0e-4,
"dominant eigenvector must be the sign-canonical (0.641423, 0.767187), got {v:?}"
);
}