1use cortiq_engine::nystrom::NystromState;
55
56fn unif(s: &mut u64) -> f32 {
57 *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
58 ((*s >> 33) as f32 / (1u64 << 31) as f32) - 1.0
59}
60
61fn trial(m: usize, w: usize, sink: usize, amp: f32, depth: usize, seed: u64) -> f32 {
64 let (d, dv) = (64usize, 8usize);
65 let t = 8 * m + w + depth;
66 let mut s = seed;
67 let rd = (d as f32).sqrt();
68
69 let mut unit = |s: &mut u64| -> Vec<f32> {
70 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
71 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
72 v.iter().map(|x| x / n).collect()
73 };
74 let qhat = unit(&mut s);
75 let q: Vec<f32> = qhat.iter().map(|x| x * rd).collect();
76
77 let mut qs = Vec::with_capacity(t * d);
78 for _ in 0..t {
79 let u = unit(&mut s);
80 qs.extend(u.iter().map(|x| x * rd));
81 }
82
83 let mut ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
84 let mut vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
85 for j in 0..t {
86 vs[j * dv] = 0.0;
87 }
88 let p = t - depth;
89 for c in 0..d {
90 ks[p * d + c] += amp * qhat[c];
91 }
92 vs[p * dv] = 1.0;
93
94 let mut st = NystromState::new(m, w, sink);
95 st.prefill(&qs, &ks, &vs, t, d, dv);
96
97 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
98 let mut v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
99 v_new[0] = 0.0;
100 let mut got = vec![0f32; dv];
101 st.step(&q, &k_new, &v_new, &mut got);
102
103 let mut logits = Vec::with_capacity(t + 1);
104 for j in 0..t {
105 let dot: f32 = (0..d).map(|c| q[c] * ks[j * d + c]).sum();
106 logits.push(dot / rd);
107 }
108 let dot: f32 = (0..d).map(|c| q[c] * k_new[c]).sum();
109 logits.push(dot / rd);
110 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
111 let (mut den, mut num0) = (0f64, 0f64);
112 for (j, &l) in logits.iter().enumerate() {
113 let e = ((l - mx) as f64).exp();
114 den += e;
115 num0 += e * if j == t { v_new[0] } else { vs[j * dv] } as f64;
116 }
117 let want0 = (num0 / den) as f32;
118 if want0.abs() < 1e-4 {
119 return f32::NAN;
120 }
121 got[0] / want0
122}
123
124
125fn background_error(m: usize, w: usize, sink: usize, depth: usize, seed: u64) -> f32 {
139 let (d, dv) = (64usize, 8usize);
140 let t = 8 * 64 + w + depth;
146 let _ = m;
147 let mut s = seed;
148 let rd = (d as f32).sqrt();
149
150 let mut unit = |s: &mut u64| -> Vec<f32> {
151 let v: Vec<f32> = (0..d).map(|_| unif(s)).collect();
152 let n = v.iter().map(|x| x * x).sum::<f32>().sqrt();
153 v.iter().map(|x| x / n).collect()
154 };
155 let q: Vec<f32> = unit(&mut s).iter().map(|x| x * rd).collect();
156 let mut qs = Vec::with_capacity(t * d);
157 for _ in 0..t {
158 let u = unit(&mut s);
159 qs.extend(u.iter().map(|x| x * rd));
160 }
161 let ks: Vec<f32> = (0..t * d).map(|_| unif(&mut s) * 1.732).collect();
162 let vs: Vec<f32> = (0..t * dv).map(|_| unif(&mut s)).collect();
163
164 let mut st = NystromState::new(m, w, sink);
165 st.prefill(&qs, &ks, &vs, t, d, dv);
166 let k_new: Vec<f32> = (0..d).map(|_| unif(&mut s) * 1.732).collect();
167 let v_new: Vec<f32> = (0..dv).map(|_| unif(&mut s)).collect();
168 let mut got = vec![0f32; dv];
169 st.step(&q, &k_new, &v_new, &mut got);
170
171 let mut logits = Vec::with_capacity(t + 1);
172 for j in 0..t {
173 logits.push((0..d).map(|c| q[c] * ks[j * d + c]).sum::<f32>() / rd);
174 }
175 logits.push((0..d).map(|c| q[c] * k_new[c]).sum::<f32>() / rd);
176 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
177 let mut den = 0f64;
178 let mut want = vec![0f64; dv];
179 for (j, &l) in logits.iter().enumerate() {
180 let e = ((l - mx) as f64).exp();
181 den += e;
182 let src = if j == t { &v_new[..] } else { &vs[j * dv..(j + 1) * dv] };
183 for (c, wc) in want.iter_mut().enumerate() {
184 *wc += e * src[c] as f64;
185 }
186 }
187 let want: Vec<f32> = want.iter().map(|x| (x / den) as f32).collect();
188 let num: f32 = got.iter().zip(&want).map(|(g, x)| (g - x) * (g - x)).sum::<f32>().sqrt();
189 let den2: f32 = want.iter().map(|x| x * x).sum::<f32>().sqrt();
190 num / den2.max(1e-9)
191}
192
193struct Cell {
194 mean: f32,
195 sd: f32,
196 detected: f32,
198 strict: f32,
200 partial: usize,
203}
204
205fn cell(m: usize, w: usize, sink: usize, amp: f32, depth: usize, trials: usize) -> Cell {
206 let mut rs = Vec::with_capacity(trials);
207 for i in 0..trials {
208 let r = trial(m, w, sink, amp, depth, 0x1000 + i as u64 * 0x9E3779B9);
209 if r.is_finite() {
210 rs.push(r);
211 }
212 }
213 let n = rs.len().max(1) as f32;
214 let mean = rs.iter().sum::<f32>() / n;
215 let var = rs.iter().map(|r| (r - mean) * (r - mean)).sum::<f32>() / n;
216 Cell {
217 mean,
218 sd: var.sqrt(),
219 detected: rs.iter().filter(|&&r| r >= 0.5).count() as f32 / n,
220 strict: rs.iter().filter(|&&r| r >= 0.95).count() as f32 / n,
221 partial: rs.iter().filter(|&&r| r > 0.05 && r < 0.95).count(),
222 }
223}
224
225fn main() {
226 let trials: usize = std::env::var("TRIALS").ok().and_then(|v| v.parse().ok()).unwrap_or(48);
227 let (w, sink, depth) = (128usize, 4usize, 512usize);
228 let ms = [4usize, 8, 16, 32, 64];
229 let amps = [2.0f32, 4.0, 6.0, 8.0, 12.0];
230
231 println!("O(1) injection/recovery — w={w} sink={sink} depth={depth}, {trials} trials/cell");
232 println!("needle logit sits `amp` background sd above the field; recovery = kept / exact\n");
233
234 println!("mean recovery");
235 print!("{:>6}", "amp\\m");
236 for m in ms {
237 print!("{m:>12}");
238 }
239 println!();
240 let mut grid = Vec::new();
241 for & in &s {
242 print!("{amp:>6.1}");
243 let mut row = Vec::new();
244 for &m in &ms {
245 let c = cell(m, w, sink, amp, depth, trials);
246 print!("{:>9.1}±{:<2.0}", c.mean * 100.0, c.sd * 100.0);
247 row.push(c);
248 }
249 println!();
250 grid.push((amp, row));
251 }
252
253 println!("\nfraction of trials at or above 95% recovery — the strict criterion");
254 print!("{:>6}", "amp\\m");
255 for m in ms {
256 print!("{m:>8}");
257 }
258 println!();
259 for (amp, row) in &grid {
260 print!("{amp:>6.1}");
261 for c in row {
262 print!("{:>7.0}%", c.strict * 100.0);
263 }
264 println!();
265 }
266
267 println!("\nfraction at or above 50% — the loose criterion");
268 print!("{:>6}", "amp\\m");
269 for m in ms {
270 print!("{m:>8}");
271 }
272 println!();
273 for (amp, row) in &grid {
274 print!("{amp:>6.1}");
275 for c in row {
276 print!("{:>7.0}%", c.detected * 100.0);
277 }
278 println!();
279 }
280
281
282 println!("\nbackground-only error (no needle) — the denominator channel isolated");
287 println!("{:>6}{:>14}{:>14}{:>12}", "m", "rel error", "sd", "log2 drop");
288 let mut prev: Option<f32> = None;
289 let mut pts: Vec<(f32, f32)> = Vec::new();
290 for &m in &ms {
291 let mut es: Vec<f32> = (0..trials)
292 .map(|i| background_error(m, w, sink, depth, 0x5000 + i as u64 * 0x9E3779B9))
293 .filter(|e| e.is_finite())
294 .collect();
295 es.sort_by(|a, b| a.partial_cmp(b).unwrap());
296 let n = es.len().max(1) as f32;
297 let mean = es.iter().sum::<f32>() / n;
298 let sd = (es.iter().map(|e| (e - mean) * (e - mean)).sum::<f32>() / n).sqrt();
299 let drop = prev.map(|p| (p / mean).log2()).unwrap_or(f32::NAN);
300 println!("{m:>6}{:>14.4}{:>14.4}{:>12.2}", mean, sd, drop);
301 prev = Some(mean);
302 pts.push(((m as f32).ln(), mean.max(1e-9).ln()));
303 }
304 let n = pts.len() as f32;
305 let (sx, sy) = pts.iter().fold((0f32, 0f32), |(a, b), (x, y)| (a + x, b + y));
306 let (mx, my) = (sx / n, sy / n);
307 let num: f32 = pts.iter().map(|(x, y)| (x - mx) * (y - my)).sum();
308 let den: f32 = pts.iter().map(|(x, _)| (x - mx) * (x - mx)).sum();
309 let beta = num / den;
310 println!(
311 "\nlog-log slope of the background error vs m: {beta:.3} (amplitude)\n\
312 its energy would be {:.3} by identity — squaring doubles any slope, which is\n\
313 why only one of the two is worth measuring. CLT on a mean of m samples predicts\n\
314 -0.500 in amplitude; a Nystrom skeleton on a slowly-decaying kernel predicts\n\
315 nearer -0.250. That is the difference this table can actually settle.",
316 2.0 * beta
317 );
318
319 println!("\nshape of the response in m");
325 for (amp, row) in &grid {
326 let m50 = ms.iter().zip(row).find(|(_, c)| c.detected >= 0.5).map(|(m, _)| *m);
327 let m95 = ms.iter().zip(row).find(|(_, c)| c.strict >= 0.5).map(|(m, _)| *m);
328 let slope = {
331 let pts: Vec<(f32, f32)> = ms
332 .iter()
333 .zip(row)
334 .filter(|(_, c)| c.mean < 0.999 && c.mean > 0.0)
335 .map(|(m, c)| ((*m as f32).ln(), (1.0 - c.mean).max(1e-4).ln()))
336 .collect();
337 if pts.len() < 2 {
338 f32::NAN
339 } else {
340 let n = pts.len() as f32;
341 let (sx, sy) = pts.iter().fold((0f32, 0f32), |(a, b), (x, y)| (a + x, b + y));
342 let (mx, my) = (sx / n, sy / n);
343 let num: f32 = pts.iter().map(|(x, y)| (x - mx) * (y - my)).sum();
344 let den: f32 = pts.iter().map(|(x, _)| (x - mx) * (x - mx)).sum();
345 num / den
346 }
347 };
348 let mid: usize = row.iter().map(|c| c.partial).sum();
349 println!(
350 "amp {amp:>4.1}: m@50% {:>6} m@95% {:>6} log-log slope of (1−r) vs m {slope:>7.2} \
351 partial trials {mid:>4}",
352 m50.map(|v| v.to_string()).unwrap_or("—".into()),
353 m95.map(|v| v.to_string()).unwrap_or("—".into()),
354 );
355 }
356 println!(
357 "\n`partial trials` counts runs landing strictly between 5% and 95% recovery, summed over \
358 the row.\nA pure step would leave that column near zero: every trial all or nothing."
359 );
360}