use nalgebra::{Matrix6, Vector6};
use rigidity_core::linalg::{reduce, singular_values};
const EPSILON: f64 = f64::EPSILON;
struct Rng(u64);
impl Rng {
fn unit(&mut self) -> f64 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
((z ^ (z >> 31)) >> 11) as f64 / (1u64 << 53) as f64
}
fn normal(&mut self) -> f64 {
let u1 = self.unit().max(f64::MIN_POSITIVE);
(-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * self.unit()).cos()
}
}
fn build(rows: usize, sigma: &[f64; 6], seed: u64) -> Vec<[f64; 6]> {
let mut rng = Rng(seed);
let mut columns: Vec<Vec<f64>> = (0..6)
.map(|_| (0..rows).map(|_| rng.normal()).collect())
.collect();
for _ in 0..2 {
for j in 0..6 {
let (left, right) = columns.split_at_mut(j);
for column in left.iter().take(j) {
let dot: f64 = right[0].iter().zip(column).map(|(a, b)| a * b).sum();
for (value, base) in right[0].iter_mut().zip(column) {
*value -= dot * base;
}
}
let norm: f64 = right[0].iter().map(|v| v * v).sum::<f64>().sqrt();
for value in right[0].iter_mut() {
*value /= norm;
}
}
}
let mut raw = Matrix6::zeros();
for i in 0..6 {
for j in 0..6 {
raw[(i, j)] = rng.normal();
}
}
let rotation = raw.qr().q();
(0..rows)
.map(|i| {
let scaled = Vector6::from_iterator((0..6).map(|j| columns[j][i] * sigma[j]));
let row = rotation * scaled;
[row[0], row[1], row[2], row[3], row[4], row[5]]
})
.collect()
}
fn spectrum_with_given_condition(power: i32) -> ([f64; 6], f64) {
let smallest = 2f64.powi(-power);
([1.0, 0.5, 0.25, 0.125, 0.0625, smallest], 1.0 / smallest)
}
fn errors(power: i32) -> (f64, f64, f64) {
let (sigma, kappa) = spectrum_with_given_condition(power);
let rows = build(20_000, &sigma, 0xC0FFEE + power as u64);
let smallest = sigma[5];
let triangle = reduce::<6, _>(rows.len(), |i| Some(rows[i]));
let from_tsqr = singular_values(triangle.triangle())[5];
let mut hessian = [[0.0f64; 6]; 6];
for row in &rows {
for i in 0..6 {
for j in 0..6 {
hessian[i][j] += row[i] * row[j];
}
}
}
let from_normal = singular_values(&hessian)[5].sqrt();
(
kappa,
(from_tsqr - smallest).abs() / smallest,
(from_normal - smallest).abs() / smallest,
)
}
#[test]
fn tsqr_error_follows_epsilon_times_kappa() {
for power in [12i32, 16, 20, 24, 28, 32, 36, 40, 44] {
let (kappa, tsqr, _) = errors(power);
let bound = 100.0 * EPSILON * kappa;
assert!(
tsqr < bound,
"κ = {kappa:.2e}: TSQR error {tsqr:.3e} exceeded the bound {bound:.3e}"
);
}
}
#[test]
fn normal_equations_error_follows_epsilon_times_kappa_squared() {
for power in [12i32, 16, 20, 24] {
let (kappa, _, normal) = errors(power);
let scale = EPSILON * kappa * kappa;
assert!(
normal < 1_000.0 * scale,
"κ = {kappa:.2e}: error {normal:.3e} above the prediction {scale:.3e}"
);
assert!(
normal > 0.01 * scale,
"κ = {kappa:.2e}: error {normal:.3e} below the prediction {scale:.3e} — \
the degradation is not quadratic, the law has changed"
);
}
}
#[test]
fn only_the_direct_path_works_at_the_f32_operating_point() {
let (kappa, tsqr, normal) = errors(24);
assert!(
(1e7..1e8).contains(&kappa),
"the operating point moved: κ = {kappa:.2e}"
);
assert!(
tsqr < 1e-8,
"TSQR gives {tsqr:.3e} at the operating point — not enough for the detector"
);
assert!(
normal > 1e-3,
"the normal equations gave {normal:.3e}: if they are accurate enough \
now, the entire motivation for TSQR needs revisiting"
);
assert!(
normal / tsqr > 1e5,
"the gap between the paths shrank to {:.1e}",
normal / tsqr
);
}
#[test]
fn normal_equations_break_down_two_orders_earlier() {
let (_, tsqr, normal) = errors(28);
assert!(
normal > 1.0,
"the normal equations are still holding: {normal:.3e}"
);
assert!(tsqr < 1e-6, "TSQR has already broken: {tsqr:.3e}");
let (_, tsqr_high, _) = errors(40);
assert!(tsqr_high < 1e-4, "TSQR at κ ≈ 10¹² gives {tsqr_high:.3e}");
}
#[test]
fn tsqr_is_bit_identical_across_thread_counts() {
let (sigma, _) = spectrum_with_given_condition(24);
let rows = build(50_000, &sigma, 0xD0D0);
let run = |threads: usize| {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap()
.install(|| reduce::<6, _>(rows.len(), |i| Some(rows[i])))
};
let reference = run(1);
let expected: Vec<u64> = reference
.triangle()
.iter()
.flat_map(|row| row.iter())
.map(|v| v.to_bits())
.collect();
for threads in [2usize, 3, 4, 8, 16] {
let result = run(threads);
let actual: Vec<u64> = result
.triangle()
.iter()
.flat_map(|row| row.iter())
.map(|v| v.to_bits())
.collect();
assert_eq!(actual, expected, "{threads} threads: R differs bit for bit");
}
}