use super::mpo::{compress_matrix_to_mpo, mpo_apply, svd_thin, Mpo};
#[derive(Debug, Clone)]
pub struct RotationMpo {
pub mpo: Mpo,
pub d: usize,
}
fn mat_vec(a: &[f32], x: &[f32], d: usize) -> Vec<f32> {
(0..d)
.map(|i| (0..d).map(|j| a[i * d + j] * x[j]).sum())
.collect()
}
fn mat_t_vec(a: &[f32], x: &[f32], d: usize) -> Vec<f32> {
(0..d)
.map(|i| (0..d).map(|j| a[j * d + i] * x[j]).sum())
.collect()
}
fn gram_schmidt(a: &[f32], d: usize) -> Vec<f32> {
let mut out = vec![0.0f32; d * d];
for i in 0..d {
let mut v: Vec<f32> = (0..d).map(|j| a[i * d + j]).collect();
let initial_norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
for k in 0..i {
let dot: f32 = (0..d).map(|j| v[j] * out[k * d + j]).sum();
for j in 0..d {
v[j] -= dot * out[k * d + j];
}
}
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
let is_degenerate = norm < 0.01 * initial_norm.max(1e-8);
if !is_degenerate {
for j in 0..d {
out[i * d + j] = v[j] / norm;
}
} else {
'find: for e in 0..d {
let mut v2 = vec![0.0f32; d];
v2[e] = 1.0;
for k in 0..i {
let dot: f32 = (0..d).map(|j| v2[j] * out[k * d + j]).sum();
for j in 0..d {
v2[j] -= dot * out[k * d + j];
}
}
let n2: f32 = v2.iter().map(|x| x * x).sum::<f32>().sqrt();
if n2 > 0.5 {
for j in 0..d {
out[i * d + j] = v2[j] / n2;
}
break 'find;
}
}
}
}
out
}
fn l2_error(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b)
.map(|(x, y)| (x - y).powi(2))
.sum::<f32>()
.sqrt()
}
pub fn empirical_query_covariance(queries: &[Vec<f32>], d: usize) -> Vec<f32> {
let mut cov = vec![0.0f32; d * d];
for q in queries {
assert_eq!(q.len(), d);
for i in 0..d {
for j in 0..d {
cov[i * d + j] += q[i] * q[j];
}
}
}
let n = queries.len() as f32;
cov.iter_mut().for_each(|c| *c /= n);
cov
}
pub fn covariance_to_rotation(cov: &[f32], d: usize) -> Vec<f32> {
let (u, _s, _vt) = svd_thin(cov, d, d);
gram_schmidt(&u, d)
}
pub fn compress_rotation(v: &[f32], d: usize, n_sites: usize, chi_max: usize) -> RotationMpo {
RotationMpo {
mpo: compress_matrix_to_mpo(v, d, d, n_sites, chi_max),
d,
}
}
pub fn apply_rotation(rot: &RotationMpo, key: &[f32]) -> Vec<f32> {
mpo_apply(&rot.mpo, key)
}
pub fn rotation_fidelity(v_exact: &[f32], rot: &RotationMpo, test_vecs: &[Vec<f32>]) -> f32 {
let d = rot.d;
let total: f32 = test_vecs
.iter()
.map(|k| {
let exact = mat_vec(v_exact, k, d);
let approx = apply_rotation(rot, k);
let dot: f32 = exact.iter().zip(&approx).map(|(a, b)| a * b).sum();
let ne: f32 = exact.iter().map(|x| x * x).sum::<f32>().sqrt();
let na: f32 = approx.iter().map(|x| x * x).sum::<f32>().sqrt();
if ne > 1e-9 && na > 1e-9 {
dot / (ne * na)
} else {
1.0
}
})
.sum();
total / test_vecs.len() as f32
}
pub fn mpo_compression_ratio_rotation(rot: &RotationMpo) -> f32 {
let original = (rot.d * rot.d) as f32;
let compressed: usize = rot
.mpo
.sites
.iter()
.map(|s| s.chi_left * s.d_out * s.d_in * s.chi_right)
.sum();
compressed as f32 / original
}
pub fn quantize_int2(x: &[f32]) -> (Vec<u8>, f32, f32) {
let min = x.iter().cloned().fold(f32::INFINITY, f32::min);
let max = x.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let scale = if (max - min).abs() < 1e-9 {
1.0
} else {
(max - min) / 3.0
};
let zero = min;
let q = x
.iter()
.map(|&v| ((v - zero) / scale).round().clamp(0.0, 3.0) as u8)
.collect();
(q, scale, zero)
}
pub fn dequantize_int2(q: &[u8], scale: f32, zero: f32) -> Vec<f32> {
q.iter().map(|&v| v as f32 * scale + zero).collect()
}
pub fn rotation_quantization_error(v: &[f32], key: &[f32], d: usize) -> f32 {
let rotated = mat_vec(v, key, d);
let (q, scale, zero) = quantize_int2(&rotated);
let dequantized = dequantize_int2(&q, scale, zero);
let recovered = mat_t_vec(v, &dequantized, d);
l2_error(key, &recovered)
}
pub fn identity_quantization_error(key: &[f32]) -> f32 {
let (q, scale, zero) = quantize_int2(key);
let dequantized = dequantize_int2(&q, scale, zero);
l2_error(key, &dequantized)
}
#[cfg(test)]
mod tests {
use super::*;
fn basis_vecs(d: usize) -> Vec<Vec<f32>> {
(0..d)
.map(|i| {
let mut e = vec![0.0f32; d];
e[i] = 1.0;
e
})
.collect()
}
#[test]
fn test_empirical_cov_rank1() {
let d = 4;
let norm = (2.0f32).sqrt();
let v_norm = vec![1.0 / norm, 0.0, 1.0 / norm, 0.0_f32];
let queries: Vec<Vec<f32>> = (0..10).map(|_| v_norm.clone()).collect();
let cov = empirical_query_covariance(&queries, d);
for i in 0..d {
for j in 0..d {
let expected = v_norm[i] * v_norm[j];
assert!(
(cov[i * d + j] - expected).abs() < 1e-5,
"cov[{i},{j}]={}, expected {expected}",
cov[i * d + j]
);
}
}
}
#[test]
fn test_empirical_cov_basis() {
let d = 4;
let queries = basis_vecs(d);
let cov = empirical_query_covariance(&queries, d);
let inv_d = 1.0 / d as f32;
for i in 0..d {
for j in 0..d {
let expected = if i == j { inv_d } else { 0.0 };
assert!(
(cov[i * d + j] - expected).abs() < 1e-5,
"cov[{i},{j}]={}, expected {expected}",
cov[i * d + j]
);
}
}
}
#[test]
fn test_rotation_orthogonal() {
let d = 4;
let queries = vec![
vec![1.0f32, 1.0, 0.0, 0.0],
vec![0.0f32, 1.0, 1.0, 0.0],
vec![1.0f32, 0.0, 0.0, 1.0],
];
let cov = empirical_query_covariance(&queries, d);
let v = covariance_to_rotation(&cov, d);
for i in 0..d {
for j in 0..d {
let dot: f32 = (0..d).map(|k| v[i * d + k] * v[j * d + k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-4,
"V*V^T[{i},{j}]={dot:.5}, expected {expected}"
);
}
}
}
#[test]
fn test_rotation_preserves_norm() {
let d = 4;
let queries = vec![vec![1.0f32, 2.0, 0.5, -1.0]];
let cov = empirical_query_covariance(&queries, d);
let v = covariance_to_rotation(&cov, d);
let key = vec![3.0f32, -1.0, 2.0, 0.5];
let rotated = mat_vec(&v, &key, d);
let norm_k: f32 = key.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_r: f32 = rotated.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!(
(norm_k - norm_r).abs() < 1e-4,
"|k|={norm_k:.4}, |Vk|={norm_r:.4}"
);
}
#[test]
fn test_compress_full_chi_exact() {
let d = 16;
let n_sites = 2;
let chi_max = 16;
let v: Vec<f32> = (0..d * d)
.map(|idx| if idx / d == idx % d { 1.0 } else { 0.0 })
.collect();
let rot = compress_rotation(&v, d, n_sites, chi_max);
let test_vecs = basis_vecs(d);
let fidelity = rotation_fidelity(&v, &rot, &test_vecs);
assert!(fidelity > 0.999, "fidelity={fidelity:.5}");
}
#[test]
fn test_fidelity_increases_with_chi() {
let d = 16;
let n_sites = 2;
let queries: Vec<Vec<f32>> = (0..8)
.map(|i| {
let mut q = vec![0.0f32; d];
q[i] = 1.0;
q[i + 8] = 0.5;
q
})
.collect();
let cov = empirical_query_covariance(&queries, d);
let v = covariance_to_rotation(&cov, d);
let test_vecs = basis_vecs(d);
let fid1 = rotation_fidelity(&v, &compress_rotation(&v, d, n_sites, 1), &test_vecs);
let fid4 = rotation_fidelity(&v, &compress_rotation(&v, d, n_sites, 4), &test_vecs);
let fid16 = rotation_fidelity(&v, &compress_rotation(&v, d, n_sites, 16), &test_vecs);
assert!(
fid4 >= fid1 - 1e-4,
"fid4={fid4:.4} should >= fid1={fid1:.4}"
);
assert!(
fid16 >= fid4 - 1e-4,
"fid16={fid16:.4} should >= fid4={fid4:.4}"
);
assert!(fid16 > 0.999, "fid16={fid16:.4} should be near 1.0");
}
#[test]
fn test_compression_ratio_small_chi() {
let d = 16;
let n_sites = 2;
let v: Vec<f32> = (0..d * d)
.map(|idx| if idx / d == idx % d { 1.0 } else { 0.0 })
.collect();
let rot2 = compress_rotation(&v, d, n_sites, 2);
let rot4 = compress_rotation(&v, d, n_sites, 4);
let r2 = mpo_compression_ratio_rotation(&rot2);
let r4 = mpo_compression_ratio_rotation(&rot4);
assert!(r2 < r4, "chi=2 ratio={r2:.3} should < chi=4 ratio={r4:.3}");
assert!(r2 < 1.0, "chi=2 should compress d=16: ratio={r2:.3}");
}
#[test]
fn test_quantize_int2_levels_in_range() {
let x = vec![-5.0f32, -3.0, 0.0, 1.0, 2.5, 7.0, 100.0, -100.0];
let (q, _scale, _zero) = quantize_int2(&x);
for &level in &q {
assert!(level <= 3, "level {level} out of range");
}
}
#[test]
fn test_quantize_int2_error_bounded() {
let x = vec![-3.7f32, -1.2, 0.3, 1.8, 3.1, -0.5, 2.2, -2.9];
let (q, scale, zero) = quantize_int2(&x);
let deq = dequantize_int2(&q, scale, zero);
for (orig, rec) in x.iter().zip(deq.iter()) {
assert!(
(orig - rec).abs() <= scale / 2.0 + 1e-4,
"error {:.4} > scale/2 {:.4}",
(orig - rec).abs(),
scale / 2.0
);
}
}
#[test]
fn test_mpo_rotation_matches_exact() {
let d = 16;
let n_sites = 2;
let queries: Vec<Vec<f32>> = (0..4)
.map(|i| {
let mut q = vec![0.0f32; d];
q[i * 4] = 1.0;
q[i * 4 + 1] = 0.7;
q
})
.collect();
let cov = empirical_query_covariance(&queries, d);
let v = covariance_to_rotation(&cov, d);
let rot = compress_rotation(&v, d, n_sites, 16);
let key = vec![
7.0, 3.0, 1.5, 1.0, 0.8, 0.6, 0.4, 0.2, 0.2, 0.4, 0.6, 0.8, 1.0, 1.5, 3.0, 7.0_f32,
];
let exact = mat_vec(&v, &key, d);
let approx = apply_rotation(&rot, &key);
let diff = l2_error(&exact, &approx);
assert!(diff < 0.01, "MPO vs exact rotation L2 diff: {diff:.5}");
}
}