Skip to content

Commit 78450a6

Browse files
authored
Merge pull request #290 from AdaWorldAPI/claude/pillar11-w1-sigker-parity
Pillar-11 W1 + W4: the cross-repo signature parity bridge, and PSD at depth-infinity
2 parents 9b3e147 + e4d3c29 commit 78450a6

9 files changed

Lines changed: 677 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,9 @@ exclude = [
424424
"crates/burn",
425425
"crates/wasm-simd-parity",
426426
"crates/neon-simd-parity",
427+
# Cross-repo: its dev-dep is a PATH into a lance-graph sibling checkout.
428+
# In-workspace, a missing sibling would fail resolution for EVERY member.
429+
"crates/sigker-parity",
427430
"vendor/chacha20",
428431
]
429432
default-members = [

crates/sigker-parity/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
Cargo.lock

crates/sigker-parity/Cargo.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# sigker-parity — the cross-repo gate for the Pillar-11 signature lanes.
2+
#
3+
# `ndarray::hpc::pillar::signature::signature_d2_deg3` (hardware: f32, fixed
4+
# d=2/deg-3, Chen accumulation) and lance-graph `sigker::signature_truncated`
5+
# (reference: f64, any d/depth) compute the SAME iterated integrals and had
6+
# zero cross-checks — census finding F-4 of
7+
# `pillar11-signature-certification-unification-v1`. The workspace's own
8+
# architecture rule (ndarray = hardware, lance-graph = thinking) blesses the
9+
# split but demands the parity test it never got. This crate is that test
10+
# (W1), plus the depth-infinity PSD leg (W4) that needs the same sibling.
11+
#
12+
# EXCLUDED from the workspace (see root Cargo.toml `exclude`) because its
13+
# `sigker` dep is a PATH into a sibling checkout. An unconditional path dep
14+
# whose target is absent fails manifest resolution for the WHOLE workspace —
15+
# ndarray CI does not check lance-graph out, so an in-workspace dep here
16+
# would break every ndarray build on a fresh clone. Excluded, it costs
17+
# nothing when the sibling is missing and runs on demand:
18+
#
19+
# cargo test --manifest-path crates/sigker-parity/Cargo.toml
20+
#
21+
# Same shape as `crates/wasm-simd-parity` and `crates/neon-simd-parity`.
22+
[package]
23+
name = "sigker-parity"
24+
version = "0.0.0"
25+
edition = "2021"
26+
publish = false
27+
28+
[dependencies]
29+
ndarray = { path = "../..", default-features = false, features = ["std", "hpc-extras", "pillar"] }
30+
31+
[dev-dependencies]
32+
sigker = { path = "../../../lance-graph/crates/sigker" }
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Diagnostic only: is the W1 gap a FORMULA difference or f32 accumulation?
2+
use ndarray::hpc::pillar::signature::signature_d2_deg3;
3+
use sigker::signature_truncated;
4+
5+
const NAMES: [&str; 15] = [
6+
"s0", "1x", "1y", "2xx", "2xy", "2yx", "2yy", "3xxx", "3xxy", "3xyx", "3xyy", "3yxx", "3yxy", "3yyx", "3yyy",
7+
];
8+
9+
fn cmp(tag: &str, flat: &[f32], n: usize) {
10+
let hw = signature_d2_deg3(flat, n);
11+
let pts: Vec<Vec<f64>> = (0..n)
12+
.map(|k| vec![flat[2 * k] as f64, flat[2 * k + 1] as f64])
13+
.collect();
14+
let refr: Vec<f64> = signature_truncated(&pts, 3)
15+
.levels
16+
.iter()
17+
.flat_map(|l| l.iter().copied())
18+
.collect();
19+
println!("--- {tag} (n={n}) ---");
20+
for i in 0..15 {
21+
let (h, r) = (hw[i] as f64, refr[i]);
22+
let d = (h - r).abs();
23+
let rel = if r.abs() > 1e-12 { d / r.abs() } else { d };
24+
if rel > 1e-6 {
25+
println!(" {:>5}: hw {:+.9} ref {:+.9} rel {:.3e} <== DIFFERS", NAMES[i], h, r, rel);
26+
}
27+
}
28+
}
29+
30+
fn main() {
31+
// Single segment: closed form, zero accumulation — any gap here is FORMULA.
32+
cmp("one segment", &[0.0, 0.0, 1.0, 0.5], 2);
33+
// Two segments: Chen composition enters.
34+
cmp("two segments", &[0.0, 0.0, 1.0, 0.5, 1.3, -0.2], 3);
35+
// Three, exact small values (representable in f32) — still formula-only.
36+
cmp("three segments", &[0.0, 0.0, 0.5, 0.25, 0.75, -0.5, 0.25, 0.125], 4);
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! Pre-registration sweep: how does the hardware-vs-reference error scale,
2+
//! and under WHICH normalization is it a stable gate?
3+
use ndarray::hpc::pillar::signature::signature_d2_deg3;
4+
use sigker::signature_truncated;
5+
6+
struct Rng(u64);
7+
impl Rng {
8+
fn f(&mut self) -> f32 {
9+
let mut x = self.0;
10+
x ^= x >> 12;
11+
x ^= x << 25;
12+
x ^= x >> 27;
13+
self.0 = x;
14+
((x.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40) as f32 / (1u32 << 24) as f32) - 0.5
15+
}
16+
}
17+
18+
// level of each of the 15 coefficients
19+
const LEVEL: [usize; 15] = [0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
20+
21+
fn main() {
22+
println!("{:>6} {:>12} {:>14} {:>16}", "N", "worst |abs|", "worst /coeff", "worst /levelmax");
23+
for &n in &[16usize, 32, 64, 128, 256] {
24+
let mut rng = Rng(0x9E37_79B9_7F4A_7C15);
25+
let (mut wa, mut wc, mut wl) = (0.0f64, 0.0f64, 0.0f64);
26+
for _ in 0..1000 {
27+
let (mut x, mut y) = (0.0f32, 0.0f32);
28+
let mut flat = Vec::with_capacity(n * 2);
29+
let mut pts = Vec::with_capacity(n);
30+
for _ in 0..n {
31+
flat.push(x);
32+
flat.push(y);
33+
pts.push(vec![x as f64, y as f64]);
34+
x += rng.f();
35+
y += rng.f();
36+
}
37+
let hw = signature_d2_deg3(&flat, n);
38+
let refr: Vec<f64> = signature_truncated(&pts, 3)
39+
.levels
40+
.iter()
41+
.flat_map(|l| l.iter().copied())
42+
.collect();
43+
// characteristic magnitude per level, from the REFERENCE
44+
let mut lvmax = [0.0f64; 4];
45+
for i in 0..15 {
46+
lvmax[LEVEL[i]] = lvmax[LEVEL[i]].max(refr[i].abs());
47+
}
48+
for i in 0..15 {
49+
let d = (hw[i] as f64 - refr[i]).abs();
50+
wa = wa.max(d);
51+
if refr[i].abs() > 1e-12 {
52+
wc = wc.max(d / refr[i].abs());
53+
}
54+
let s = lvmax[LEVEL[i]].max(1e-12);
55+
wl = wl.max(d / s);
56+
}
57+
}
58+
println!("{n:>6} {wa:>12.3e} {wc:>14.3e} {wl:>16.3e}");
59+
}
60+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Does depth-inf self-kernel concentration shrink like 1/sqrt(N) (a sample-
2+
//! size effect) or plateau (a genuine heavy tail)? Measure, do not assume.
3+
use ndarray::hpc::pillar::signature::brownian_path_d2;
4+
use ndarray::hpc::pillar::SplitMix64;
5+
use sigker::{signature_kernel_pde, signature_truncated};
6+
7+
const SEED: u64 = 0x5EED_1111_5164_A7AB;
8+
const N_STEPS: usize = 50;
9+
10+
fn pool(n: usize) -> Vec<Vec<Vec<f64>>> {
11+
let mut rng = SplitMix64::new(SEED);
12+
(0..n)
13+
.map(|_| {
14+
let p = brownian_path_d2(&mut rng, N_STEPS);
15+
(0..=N_STEPS)
16+
.map(|k| vec![p[2 * k] as f64, p[2 * k + 1] as f64])
17+
.collect()
18+
})
19+
.collect()
20+
}
21+
22+
fn stats(v: &[f64]) -> (f64, f64, f64) {
23+
let n = v.len();
24+
let h = n / 2;
25+
let m1 = v[..h].iter().sum::<f64>() / h as f64;
26+
let m2 = v[h..].iter().sum::<f64>() / (n - h) as f64;
27+
let mean = v.iter().sum::<f64>() / n as f64;
28+
let var = v.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
29+
// half-mean gap, and the coefficient of variation that predicts it
30+
((m1 - m2).abs() / mean, var.sqrt() / mean, mean)
31+
}
32+
33+
fn main() {
34+
println!("depth-INFINITY (Goursat PDE)");
35+
println!("{:>6} {:>12} {:>10} {:>14} {:>12}", "N", "concentr", "CV", "predicted", "mean K");
36+
for &n in &[64usize, 128, 256, 512, 1000] {
37+
let p = pool(n);
38+
let k: Vec<f64> = p.iter().map(|x| signature_kernel_pde(x, x)).collect();
39+
let (c, cv, mean) = stats(&k);
40+
// For independent samples the expected half-mean gap ~ CV * sqrt(8/(pi*N))
41+
let pred = cv * (8.0 / (core::f64::consts::PI * n as f64)).sqrt();
42+
println!("{n:>6} {c:>12.4} {cv:>10.3} {pred:>14.4} {mean:>12.4e}");
43+
}
44+
println!("\ndepth-3 TRUNCATED (the existing battery's kernel, f64 reference)");
45+
println!("{:>6} {:>12} {:>10} {:>14} {:>12}", "N", "concentr", "CV", "predicted", "mean K");
46+
for &n in &[64usize, 1000] {
47+
let p = pool(n);
48+
let k: Vec<f64> = p
49+
.iter()
50+
.map(|x| {
51+
let s = signature_truncated(x, 3);
52+
s.levels
53+
.iter()
54+
.flat_map(|l| l.iter())
55+
.map(|v| v * v)
56+
.sum::<f64>()
57+
})
58+
.collect();
59+
let (c, cv, mean) = stats(&k);
60+
let pred = cv * (8.0 / (core::f64::consts::PI * n as f64)).sqrt();
61+
println!("{n:>6} {c:>12.4} {cv:>10.3} {pred:>14.4} {mean:>12.4e}");
62+
}
63+
}

crates/sigker-parity/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn sibling_is_wired() -> bool {
2+
true
3+
}

0 commit comments

Comments
 (0)