|
| 1 | +//! Golden-helix anti-theater probe — does the irrational (golden-angle) sampling |
| 2 | +//! and Fisher-z percentile rank earn their keep, or is the 2×2/4×4 perturbation |
| 3 | +//! just "eigenvalue theater"? |
| 4 | +//! |
| 5 | +//! Two load-bearing claims from the architecture, each measured against a null: |
| 6 | +//! |
| 7 | +//! 1. COLLAPSE-AVOIDANCE (the Fujifilm-X-Trans / golden-ratio point). |
| 8 | +//! The helix places nodes on a hemisphere via the golden angle |
| 9 | +//! `γ = π(3−√5)`, `θ = ½·arccos(1 − 2(n+0.5)/N)`, `φ = n·γ`. An irrational |
| 10 | +//! stride is a low-discrepancy sampler: it should MAXIMISE the minimum |
| 11 | +//! nearest-neighbour gap (no two nodes collapse together) and keep the |
| 12 | +//! nearest-neighbour distances uniform (low coefficient of variation), |
| 13 | +//! BEATING both a regular (θ,φ) grid (which clumps at the pole) and uniform |
| 14 | +//! random (which clumps everywhere — Poisson). If golden does NOT beat both, |
| 15 | +//! the irrational stride is theater. Measured: min-gap (bigger = better) and |
| 16 | +//! NN-distance CoV (smaller = more uniform). |
| 17 | +//! |
| 18 | +//! 2. NO-COSINE NORMALISED KEY (palette256 Fisher-z Prozentrang). |
| 19 | +//! `fisher_z(s) = ½·ln((1+s)/(1-s)) = arctanh(s)` is strictly monotone in the |
| 20 | +//! cosine `s`, and a percentile rank of a monotone transform is monotone in |
| 21 | +//! `s` too — so the rank preserves EVERY pairwise similarity ordering |
| 22 | +//! (Spearman = 1) while being a normalised [0,1] key you can compare directly |
| 23 | +//! without ever re-materialising a cosine. Fisher-z additionally stretches |
| 24 | +//! the rim (high-|s|) so equal rank steps carry equal discriminability. |
| 25 | +//! Measured: ordering preservation, and rim-vs-centre resolution gain. |
| 26 | +//! |
| 27 | +//! cargo run --release --example golden_helix_probe |
| 28 | +
|
| 29 | +const GAMMA: f64 = std::f64::consts::PI * (3.0 - 2.2360679774997896); // π(3−√5), golden angle |
| 30 | + |
| 31 | +/// Golden-spiral hemisphere unit vectors (the helix node directions). |
| 32 | +fn golden_hemisphere(n: usize) -> Vec<[f64; 3]> { |
| 33 | + (0..n) |
| 34 | + .map(|i| { |
| 35 | + let theta = 0.5 * (1.0 - 2.0 * (i as f64 + 0.5) / n as f64).acos(); // polar ∈ [0, π/2] |
| 36 | + let phi = (i as f64 * GAMMA) % (2.0 * std::f64::consts::PI); |
| 37 | + [theta.sin() * phi.cos(), theta.sin() * phi.sin(), theta.cos()] |
| 38 | + }) |
| 39 | + .collect() |
| 40 | +} |
| 41 | + |
| 42 | +/// Regular (θ,φ) grid on the hemisphere — the "rational stride" null (clumps at pole). |
| 43 | +fn regular_hemisphere(n: usize) -> Vec<[f64; 3]> { |
| 44 | + let side = (n as f64).sqrt().round() as usize; |
| 45 | + let mut v = Vec::with_capacity(side * side); |
| 46 | + for a in 0..side { |
| 47 | + for b in 0..side { |
| 48 | + let theta = 0.5 * std::f64::consts::PI * (a as f64 + 0.5) / side as f64; |
| 49 | + let phi = 2.0 * std::f64::consts::PI * (b as f64 + 0.5) / side as f64; |
| 50 | + v.push([theta.sin() * phi.cos(), theta.sin() * phi.sin(), theta.cos()]); |
| 51 | + } |
| 52 | + } |
| 53 | + v |
| 54 | +} |
| 55 | + |
| 56 | +/// Uniform-random hemisphere (area-correct) — the Poisson-clumping null. |
| 57 | +fn random_hemisphere(n: usize, seed: &mut u64) -> Vec<[f64; 3]> { |
| 58 | + let mut u = || { |
| 59 | + *seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15); |
| 60 | + let mut z = *seed; |
| 61 | + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); |
| 62 | + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); |
| 63 | + ((z ^ (z >> 31)) >> 11) as f64 / (1u64 << 53) as f64 |
| 64 | + }; |
| 65 | + (0..n) |
| 66 | + .map(|_| { |
| 67 | + let z = u(); // cosθ uniform in [0,1] ⇒ area-uniform on the hemisphere |
| 68 | + let phi = 2.0 * std::f64::consts::PI * u(); |
| 69 | + let r = (1.0 - z * z).sqrt(); |
| 70 | + [r * phi.cos(), r * phi.sin(), z] |
| 71 | + }) |
| 72 | + .collect() |
| 73 | +} |
| 74 | + |
| 75 | +/// (min nearest-neighbour angle, CoV of nearest-neighbour angles). Angle = great-circle. |
| 76 | +fn nn_stats(pts: &[[f64; 3]]) -> (f64, f64) { |
| 77 | + let n = pts.len(); |
| 78 | + let mut nn = vec![f64::INFINITY; n]; |
| 79 | + for i in 0..n { |
| 80 | + for j in (i + 1)..n { |
| 81 | + let dot = (pts[i][0] * pts[j][0] + pts[i][1] * pts[j][1] + pts[i][2] * pts[j][2]).clamp(-1.0, 1.0); |
| 82 | + let ang = dot.acos(); |
| 83 | + if ang < nn[i] { |
| 84 | + nn[i] = ang; |
| 85 | + } |
| 86 | + if ang < nn[j] { |
| 87 | + nn[j] = ang; |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + let min = nn.iter().cloned().fold(f64::INFINITY, f64::min); |
| 92 | + let mean = nn.iter().sum::<f64>() / n as f64; |
| 93 | + let var = nn.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / n as f64; |
| 94 | + (min, var.sqrt() / mean) // (min gap, coefficient of variation) |
| 95 | +} |
| 96 | + |
| 97 | +fn fisher_z(s: f64) -> f64 { |
| 98 | + let s = s.clamp(-1.0 + 1e-9, 1.0 - 1e-9); |
| 99 | + 0.5 * ((1.0 + s) / (1.0 - s)).ln() |
| 100 | +} |
| 101 | + |
| 102 | +fn main() { |
| 103 | + println!("== Golden-helix anti-theater probe ==\n"); |
| 104 | + |
| 105 | + println!("[1] Collapse-avoidance — min NN gap (rad, BIGGER better) + CoV (SMALLER better):"); |
| 106 | + println!(" N golden(min/CoV) regular(min/CoV) random(min/CoV) golden wins?"); |
| 107 | + let mut seed = 0xABCDEF; |
| 108 | + for &n in &[16usize, 64, 256, 1024] { |
| 109 | + let (gm, gc) = nn_stats(&golden_hemisphere(n)); |
| 110 | + let (rm, rc) = nn_stats(®ular_hemisphere(n)); |
| 111 | + let (xm, xc) = nn_stats(&random_hemisphere(n, &mut seed)); |
| 112 | + // "Wins" = golden has the largest min-gap AND the lowest CoV. |
| 113 | + let wins = gm >= rm && gm >= xm && gc <= rc && gc <= xc; |
| 114 | + println!( |
| 115 | + " {n:>5} {gm:.4}/{gc:.3} {rm:.4}/{rc:.3} {xm:.4}/{xc:.3} {}", |
| 116 | + if wins { "YES" } else { "no" } |
| 117 | + ); |
| 118 | + } |
| 119 | + |
| 120 | + println!("\n[2] Fisher-z percentile rank as a no-cosine normalised key:"); |
| 121 | + // A deterministic spread of cosine similarities in (−1, 1). |
| 122 | + let mut sims: Vec<f64> = (0..1000).map(|i| -0.999 + 1.998 * (i as f64 + 0.5) / 1000.0).collect(); |
| 123 | + // Percentile rank of fisher_z(s). Both fisher_z and ranking are monotone in s, |
| 124 | + // so the rank order must equal the cosine order — verify (Spearman == 1). |
| 125 | + let mut idx: Vec<usize> = (0..sims.len()).collect(); |
| 126 | + idx.sort_by(|&a, &b| fisher_z(sims[a]).partial_cmp(&fisher_z(sims[b])).unwrap()); |
| 127 | + let inversions = idx.windows(2).filter(|w| sims[w[0]] > sims[w[1]]).count(); |
| 128 | + println!(" rank-order vs cosine-order inversions: {inversions} (0 ⇒ ordering fully preserved, no cosine needed)"); |
| 129 | + |
| 130 | + // Rim-stretch: resolution (Δz per unit Δs) near the rim vs the centre. |
| 131 | + sims.sort_by(|a, b| a.partial_cmp(b).unwrap()); |
| 132 | + let res = |s: f64| (fisher_z(s + 0.01) - fisher_z(s - 0.01)) / 0.02; |
| 133 | + let centre = res(0.0); |
| 134 | + let rim = res(0.9); |
| 135 | + println!( |
| 136 | + " Fisher-z resolution: centre(s=0.0) = {centre:.2}/unit, rim(s=0.9) = {rim:.2}/unit → rim gets {:.1}× more bits", |
| 137 | + rim / centre |
| 138 | + ); |
| 139 | + println!(" ⇒ percentile rank ∈ [0,1] is a normalised similarity key; compare ranks directly,"); |
| 140 | + println!(" never re-materialising cosine, with extra resolution where similarity is high."); |
| 141 | +} |
0 commit comments