use nalgebra::{Matrix4, Vector4};
use proptest::prelude::*;
pub const N: usize = 4;
pub const ALGORITHM_TOL: f64 = 1e-10;
pub const ASSERTION_TOL: f64 = 1e-8;
pub const SINGULAR_TOL: f64 = 1e-12;
pub fn algorithm_tol_f32() -> f32 {
f32::EPSILON.sqrt()
}
pub fn assertion_tol_f32() -> f32 {
100.0 * f32::EPSILON.sqrt()
}
pub fn overlap(v: &[f64], w: &[f64]) -> f64 {
let dot: f64 = v.iter().zip(w.iter()).map(|(x, y)| x * y).sum();
let norm_v: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm_w: f64 = w.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm_v > 0.0 && norm_w > 0.0 {
dot / (norm_v * norm_w)
} else {
0.0
}
}
pub fn approx_eq_eigenvector(v: &[f64], w: &[f64], tol: f64) -> bool {
overlap(v, w).abs() >= 1.0 - tol
}
pub fn max_iter_for(convergence_ratio: f64, tol: f64) -> usize {
((tol.ln() / convergence_ratio.ln()).ceil() as usize).max(5) * 2
}
pub fn dominant_ratio(eigenvalues: &[f64; N]) -> f64 {
let dominant = eigenvalues[0].abs();
eigenvalues[1..]
.iter()
.fold(0.0_f64, |acc, x| acc.max(x.abs() / dominant))
}
prop_compose! {
pub fn spectrum_with_gap()(
magnitude in 0.5..50.0f64,
negate in any::<bool>(),
ratios in prop::array::uniform3(-0.98..0.98f64),
) -> [f64; N] {
let lambda_1 = if negate { -magnitude } else { magnitude };
let mut eigenvalues = [lambda_1, 0.0, 0.0, 0.0];
for (slot, ratio) in eigenvalues[1..].iter_mut().zip(ratios) {
let value = ratio * lambda_1;
*slot = if value.abs() < 0.01 {
if value >= 0.0 { 0.01 } else { -0.01 }
} else {
value
};
}
eigenvalues
}
}
fn orthogonal_from(entries: &[f64; 16]) -> Matrix4<f64> {
Matrix4::from_row_slice(entries).qr().q()
}
fn row_major(m: &Matrix4<f64>) -> [f64; 16] {
let mut out = [0.0; 16];
for r in 0..N {
for c in 0..N {
out[r * N + c] = m[(r, c)];
}
}
out
}
pub fn column(m: &Matrix4<f64>, index: usize) -> [f64; N] {
[m[(0, index)], m[(1, index)], m[(2, index)], m[(3, index)]]
}
pub fn symmetric_with_spectrum(
eigenvalues: &[f64; N],
q_seed: &[f64; 16],
) -> ([f64; 16], Matrix4<f64>) {
let q = orthogonal_from(q_seed);
let d = Matrix4::from_diagonal(&Vector4::from_row_slice(eigenvalues));
let a = q * d * q.transpose();
(row_major(&a), q)
}
pub fn nonsymmetric_with_spectrum(
eigenvalues: &[f64; N],
q_seed: &[f64; 16],
upper: &[f64; 6],
) -> Option<([f64; 16], Matrix4<f64>, Matrix4<f64>)> {
let q = orthogonal_from(q_seed);
let mut u = Matrix4::identity();
let mut next = 0;
for r in 0..N {
for c in (r + 1)..N {
u[(r, c)] = upper[next];
next += 1;
}
}
let p = q * u;
let p_inv = p.try_inverse()?;
let d = Matrix4::from_diagonal(&Vector4::from_row_slice(eigenvalues));
let a = p * d * p_inv;
Some((row_major(&a), p, p_inv))
}
pub fn eigenbasis_coordinate(p_inv: &Matrix4<f64>, v: &[f64; N], index: usize) -> f64 {
let coefficient = (p_inv * Vector4::from_row_slice(v))[index];
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > 0.0 { coefficient / norm } else { 0.0 }
}
fn fixed_orthogonal_3() -> [[f64; 3]; 3] {
let w = [1.0, 2.0, 3.0];
let w_dot_w = 14.0;
let mut h = [[0.0; 3]; 3];
for r in 0..3 {
for c in 0..3 {
let identity = if r == c { 1.0 } else { 0.0 };
h[r][c] = identity - 2.0 * w[r] * w[c] / w_dot_w;
}
}
h
}
pub fn fixed_similarity_3(eigenvalues: [f64; 3]) -> [f64; 9] {
let h = fixed_orthogonal_3();
let mut a = [0.0; 9];
for r in 0..3 {
for c in 0..3 {
a[r * 3 + c] = (0..3).map(|k| h[r][k] * eigenvalues[k] * h[c][k]).sum();
}
}
a
}
pub fn fixed_eigenvector_3(index: usize) -> [f64; 3] {
let h = fixed_orthogonal_3();
[h[0][index], h[1][index], h[2][index]]
}