#![allow(clippy::cast_precision_loss, clippy::cast_possible_wrap)]
use super::*;
#[test]
fn tt_round_rank1_stays_rank1() {
let f1: Vec<f64> = (0..8).map(|i| f64::from(i).sin()).collect();
let f2: Vec<f64> = (0..8).map(|i| f64::from(i).cos()).collect();
let state = TtState::rank1_separable(vec![f1.clone(), f2.clone()]);
let mut cores = state.cores.clone();
tt_round(&mut cores, 1e-10);
assert_eq!(cores[0].r_right, 1, "rank-1 tensor rounded to rank > 1");
assert_eq!(cores[1].r_left, 1);
}
#[test]
fn rank1_heat_matches_gaussian_mean() {
let n = 64usize;
let xmin = -5.0f64;
let xmax = 5.0f64;
let dx = (xmax - xmin) / (n as f64 - 1.0);
let f: Vec<f64> = (0..n)
.map(|i| {
let x = xmin + i as f64 * dx;
(-x * x / 4.0).exp()
})
.collect();
let state0 = TtState::rank1_separable(vec![f.clone(), f.clone()]);
let a_x = 0.5f64;
let a_y = 0.7f64;
let t_final = 0.5f64;
let n_steps = 100usize;
let evolver = TtChernoff::new(
vec![a_x, a_y],
vec![0.0, 0.0],
0.0,
vec![(xmin, xmax), (xmin, xmax)],
1e-10,
);
let mut state = state0;
evolver.evolve(t_final, n_steps, &mut state);
assert_eq!(state.peak_rank(), 1, "diagonal heat should preserve rank-1");
let ones: Vec<f64> = vec![1.0; n];
let mass = state.inner_separable(&[ones.clone(), ones.clone()]);
let init_discrete: f64 = f.iter().sum::<f64>();
let init_mass_2d = init_discrete * init_discrete;
let rel_diff = (mass - init_mass_2d).abs() / init_mass_2d.abs().max(1e-10);
assert!(
rel_diff < 0.15,
"mass conservation failed: init={init_mass_2d:.4e} final={mass:.4e} rel={rel_diff:.3}"
);
}
#[test]
#[allow(clippy::similar_names)]
fn tt_round_rank2_preserves_inner_product() {
let n = 16usize;
let f1: Vec<f64> = (0..n).map(|i| i as f64 / n as f64).collect();
let f2: Vec<f64> = (0..n).map(|i| -(i as f64 / n as f64)).collect();
let g1: Vec<f64> = (0..n).map(|i| (i as f64 / n as f64).powi(2)).collect();
let g2: Vec<f64> = (0..n).map(|i| (n - i) as f64 / n as f64).collect();
let mut core0 = TtCore::zeros(1, n, 2);
for i in 0..n {
core0.set(0, i, 0, f1[i]);
core0.set(0, i, 1, f2[i]);
}
let mut core1 = TtCore::zeros(2, n, 1);
for i in 0..n {
core1.set(0, i, 0, g1[i]);
core1.set(1, i, 0, g2[i]);
}
let state = TtState {
cores: vec![core0, core1],
};
let ones = vec![1.0f64; n];
let expected = state.inner_separable(&[ones.clone(), ones.clone()]);
let mut cores = state.cores.clone();
tt_round(&mut cores, 1e-14);
let state2 = TtState { cores };
let actual = state2.inner_separable(&[ones.clone(), ones.clone()]);
let err = (expected - actual).abs();
assert!(
err < 1e-10,
"rounding destroyed inner product: expected={expected:.6e} got={actual:.6e} err={err:.2e}"
);
}
#[test]
fn tt_chernoff_byte_reproducible() {
let n = 32usize;
let xmin = -4.0f64;
let xmax = 4.0f64;
let dx = (xmax - xmin) / (n as f64 - 1.0);
let f: Vec<f64> = (0..n)
.map(|i| {
let x = xmin + i as f64 * dx;
(-x * x / 2.0).exp()
})
.collect();
let evolver = TtChernoff::new(
vec![0.5, 0.3],
vec![0.0, 0.0],
0.0,
vec![(xmin, xmax), (xmin, xmax)],
1e-10,
);
let mut s1 = TtState::rank1_separable(vec![f.clone(), f.clone()]);
let mut s2 = TtState::rank1_separable(vec![f.clone(), f.clone()]);
evolver.evolve(0.2, 20, &mut s1);
evolver.evolve(0.2, 20, &mut s2);
for (k, (c1, c2)) in s1.cores.iter().zip(s2.cores.iter()).enumerate() {
assert_eq!(
c1.data, c2.data,
"core {k} not bit-identical between two runs"
);
}
}
#[test]
fn rank1_heat_3d_rank_preserved() {
let n = 32usize;
let xmin = -4.0f64;
let xmax = 4.0f64;
let dx = (xmax - xmin) / (n as f64 - 1.0);
let f: Vec<f64> = (0..n)
.map(|i| {
let x = xmin + i as f64 * dx;
(-x * x / 2.0).exp()
})
.collect();
let evolver = TtChernoff::new(
vec![0.5, 0.3, 0.4],
vec![0.0, 0.0, 0.0],
0.0,
vec![(xmin, xmax), (xmin, xmax), (xmin, xmax)],
1e-10,
);
let mut state = TtState::rank1_separable(vec![f.clone(), f.clone(), f.clone()]);
evolver.evolve(0.3, 30, &mut state);
assert_eq!(
state.peak_rank(),
1,
"diagonal heat should preserve rank-1 in 3D"
);
assert_eq!(state.ndim(), 3);
}