Skip to content

Commit 77ac709

Browse files
committed
test(hpc): MTMM instrument probe — syntax=angle ⊥ semantics=residue ⊥ basin=phase
Treats the syntax/semantics/episodic → angle/residue/phase reframing as a multitrait-multimethod hypothesis and tests it with the reliability suite (Spearman + ICC), rather than assuming it. Result over a synthetic population with independent latent factors: - convergent: syn↔angle ρ=1.000, sem↔residue ρ=1.000 - discriminant: off-diagonal ρ≈0.015 — angle ⊥ residue (genuinely orthogonal) - phase independent of content (ρ≈0); golden phase 32× better min-gap than random - RELIABILITY: angle ICC=0.998, phase=1.0, but residue ICC=0.471 (FAIL @ noise 0.18) The probe earns its keep by falsifying one cell: location-residue is a small-signal measure (deviation a−centroid, norm ~0–1.5) vs angle on full vectors (norm ~6), so √D L2 noise swamps it — empirical I-NOISE-FLOOR-JIRAK. Next: directional/per-dim residue + Belichtungsmesser σ-gate to recover ICC. Synthetic instrument-shape test only (orthogonality/reliability/separation); the linguistic mapping still needs a real-text (deepnsm/COCA) probe. https://claude.ai/code/session_01D2WSmezQBNC3bUdHuGfGmo
1 parent 83be7c3 commit 77ac709

2 files changed

Lines changed: 215 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ required-features = ["std"]
7777
name = "entropy_ladder_probe"
7878
required-features = ["std"]
7979

80+
[[example]]
81+
name = "instrument_mtmm_probe"
82+
required-features = ["std"]
83+
8084
[dependencies]
8185
num-integer = { workspace = true }
8286
num-traits = { workspace = true }

examples/instrument_mtmm_probe.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//! Measurement-instrument hypothesis probe (MTMM) — syntax=angle, semantics=
2+
//! location-residue, episodic-basin=phase. Tests the THREE as an instrument, not
3+
//! as assumed fact, via the reliability suite (Spearman + ICC).
4+
//!
5+
//! The hypothesis: these are three ORTHOGONAL measurement axes. The probe builds
6+
//! a synthetic population where three latent factors vary independently — `syn`
7+
//! (a pairwise relation = the angle imposed between s and o), `sem` (a semantic
8+
//! location = residue magnitude from a centroid), and `epi` (an episode index →
9+
//! golden phase) — then measures each axis from the fingerprints and asks the
10+
//! reliability suite four multitrait-multimethod questions:
11+
//!
12+
//! - CONVERGENT: does each measure track its own factor? (diagonal high)
13+
//! - DISCRIMINANT: does it stay flat on the others? (off-diagonal ~0)
14+
//! - RELIABLE: is it stable under observation noise? (ICC test-retest)
15+
//! - PHASE SEPARATES: does the golden phase spread episodes? (anti-collapse)
16+
//!
17+
//! A clean positive = the reframing is a real instrument. Any failed cell = the
18+
//! operationalization conflates and must be corrected (not assumed).
19+
//!
20+
//! cargo run --release --example instrument_mtmm_probe --features std
21+
22+
use ndarray::hpc::reliability::{icc_a1, spearman};
23+
24+
const D: usize = 24;
25+
const K: usize = 8;
26+
27+
fn splitmix(s: &mut u64) -> f64 {
28+
*s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
29+
let mut z = *s;
30+
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
31+
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
32+
z ^= z >> 31;
33+
(z >> 11) as f64 / (1u64 << 53) as f64
34+
}
35+
36+
fn randn(s: &mut u64) -> f64 {
37+
// Box-Muller.
38+
let u1 = splitmix(s).max(1e-12);
39+
let u2 = splitmix(s);
40+
(-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos()
41+
}
42+
43+
fn dot(a: &[f64], b: &[f64]) -> f64 {
44+
a.iter().zip(b).map(|(x, y)| x * y).sum()
45+
}
46+
fn norm(a: &[f64]) -> f64 {
47+
dot(a, a).sqrt()
48+
}
49+
fn unit(s: &mut u64) -> Vec<f64> {
50+
let v: Vec<f64> = (0..D).map(|_| randn(s)).collect();
51+
let n = norm(&v).max(1e-12);
52+
v.iter().map(|x| x / n).collect()
53+
}
54+
55+
fn angle_between(a: &[f64], b: &[f64]) -> f64 {
56+
(dot(a, b) / (norm(a) * norm(b)).max(1e-12))
57+
.clamp(-1.0, 1.0)
58+
.acos()
59+
}
60+
61+
fn residue_to_nearest(v: &[f64], centroids: &[Vec<f64>]) -> f64 {
62+
centroids
63+
.iter()
64+
.map(|c| {
65+
v.iter()
66+
.zip(c)
67+
.map(|(x, y)| (x - y) * (x - y))
68+
.sum::<f64>()
69+
.sqrt()
70+
})
71+
.fold(f64::INFINITY, f64::min)
72+
}
73+
74+
/// Min circular gap of phases in [0, 2π) — the basin-separation metric.
75+
fn min_circular_gap(phases: &[f64]) -> f64 {
76+
let mut p: Vec<f64> = phases.to_vec();
77+
p.sort_by(|a, b| a.partial_cmp(b).unwrap());
78+
let mut min = f64::INFINITY;
79+
for i in 0..p.len() {
80+
let gap = if i + 1 < p.len() {
81+
p[i + 1] - p[i]
82+
} else {
83+
p[0] + std::f64::consts::TAU - p[i]
84+
};
85+
min = min.min(gap);
86+
}
87+
min
88+
}
89+
90+
fn main() {
91+
println!("== Measurement-instrument MTMM probe: syntax=angle · semantics=residue · basin=phase ==\n");
92+
let mut s = 0x5EED_4A2B_u64;
93+
let golden = std::f64::consts::PI * (3.0 - 5.0_f64.sqrt());
94+
95+
// Well-separated semantic centroids (spacing >> residue range R so the
96+
// nearest-centroid stays the generating one — keeps residue = a clean
97+
// location measure).
98+
let centroids: Vec<Vec<f64>> = (0..K)
99+
.map(|_| unit(&mut s).iter().map(|x| x * 6.0).collect())
100+
.collect();
101+
let r_max = 1.5;
102+
let noise = 0.18; // observation noise for the test-retest reliability leg
103+
104+
let n = 6000usize;
105+
let (mut f_syn, mut f_sem, mut f_epi) = (vec![], vec![], vec![]);
106+
let (mut m_angle, mut m_res, mut m_phase) = (vec![], vec![], vec![]);
107+
let (mut m_angle2, mut m_res2) = (vec![], vec![]); // noisy retest
108+
let mut golden_phase = vec![];
109+
let mut random_phase = vec![];
110+
111+
for i in 0..n {
112+
let c = (splitmix(&mut s) * K as f64) as usize % K;
113+
let sem = splitmix(&mut s) * r_max; // semantic residue magnitude
114+
let syn = 0.15 + splitmix(&mut s) * (std::f64::consts::PI - 0.30); // relation angle
115+
let epi = i as f64;
116+
117+
// s_fp = centroid + sem·dir (its residue from the nearest centroid = sem).
118+
let dir = unit(&mut s);
119+
let s_fp: Vec<f64> = centroids[c]
120+
.iter()
121+
.zip(&dir)
122+
.map(|(cc, d)| cc + sem * d)
123+
.collect();
124+
// o_fp imposes EXACTLY angle `syn` to s_fp, independent of s_fp's location:
125+
// o = cos(syn)·s + sin(syn)·|s|·perp, perp ⊥ s.
126+
let rnd = unit(&mut s);
127+
let proj = dot(&rnd, &s_fp) / dot(&s_fp, &s_fp).max(1e-12);
128+
let mut perp: Vec<f64> = rnd.iter().zip(&s_fp).map(|(r, sf)| r - proj * sf).collect();
129+
let pn = norm(&perp).max(1e-12);
130+
for x in perp.iter_mut() {
131+
*x /= pn;
132+
}
133+
let sn = norm(&s_fp);
134+
let o_fp: Vec<f64> = s_fp
135+
.iter()
136+
.zip(&perp)
137+
.map(|(sf, pp)| syn.cos() * sf + syn.sin() * sn * pp)
138+
.collect();
139+
140+
// Clean measures.
141+
m_angle.push(angle_between(&s_fp, &o_fp));
142+
m_res.push(residue_to_nearest(&s_fp, &centroids));
143+
let ph = (epi * golden).rem_euclid(std::f64::consts::TAU);
144+
m_phase.push(ph);
145+
146+
// Noisy retest (observation noise on both fingerprints).
147+
let s2: Vec<f64> = s_fp.iter().map(|x| x + noise * randn(&mut s)).collect();
148+
let o2: Vec<f64> = o_fp.iter().map(|x| x + noise * randn(&mut s)).collect();
149+
m_angle2.push(angle_between(&s2, &o2));
150+
m_res2.push(residue_to_nearest(&s2, &centroids));
151+
152+
f_syn.push(syn);
153+
f_sem.push(sem);
154+
f_epi.push(epi);
155+
golden_phase.push(ph);
156+
random_phase.push(splitmix(&mut s) * std::f64::consts::TAU);
157+
}
158+
159+
// ── MTMM Spearman matrix (factor × measure) ──
160+
let sa = spearman(&f_syn, &m_angle);
161+
let sr = spearman(&f_syn, &m_res);
162+
let ea = spearman(&f_sem, &m_angle);
163+
let er = spearman(&f_sem, &m_res);
164+
let pa = spearman(&f_epi, &m_angle);
165+
let pr = spearman(&f_epi, &m_res);
166+
167+
println!("MTMM Spearman ρ (factor ↓ × measure →): [convergent=diagonal, discriminant=off-diagonal]");
168+
println!(" angle residue");
169+
println!(" syn (relation) {sa:+.3} {sr:+.3}");
170+
println!(" sem (location) {ea:+.3} {er:+.3}");
171+
println!(" epi (episode) {pa:+.3} {pr:+.3}");
172+
173+
// Phase independence + reliability + separation.
174+
let ph_syn = spearman(&f_syn, &m_phase);
175+
let ph_sem = spearman(&f_sem, &m_phase);
176+
let icc_angle = icc_a1(&[&m_angle, &m_angle2]);
177+
let icc_res = icc_a1(&[&m_res, &m_res2]);
178+
let prefix = 64usize;
179+
let g_gap = min_circular_gap(&golden_phase[..prefix]);
180+
let r_gap = min_circular_gap(&random_phase[..prefix]);
181+
182+
println!("\nphase independence: ρ(syn,phase)={ph_syn:+.3} ρ(sem,phase)={ph_sem:+.3} (≈0 expected)");
183+
println!("reliability (ICC test-retest @ noise {noise}): angle={icc_angle:.3} residue={icc_res:.3} phase=1.000 (deterministic)");
184+
println!(
185+
"phase separation (min circular gap, first {prefix}): golden={g_gap:.4} random={r_gap:.4} ({:.1}× better)",
186+
g_gap / r_gap.max(1e-9)
187+
);
188+
189+
// ── Verdict ──
190+
let convergent = sa >= 0.9 && er >= 0.9;
191+
let discriminant = ea.abs() <= 0.2 && sr.abs() <= 0.2;
192+
let phase_indep = ph_syn.abs() <= 0.2 && ph_sem.abs() <= 0.2;
193+
let reliable = icc_angle >= 0.7 && icc_res >= 0.7;
194+
let separates = g_gap > 1.5 * r_gap;
195+
let mark = |b: bool| if b { "PASS" } else { "FAIL" };
196+
println!("\nVERDICT:");
197+
println!(" convergent (each measure tracks its factor) ......... {}", mark(convergent));
198+
println!(" discriminant (axes do not leak into each other) ..... {}", mark(discriminant));
199+
println!(" phase independent of content .........................{}", mark(phase_indep));
200+
println!(" reliable under noise (ICC ≥ 0.7) .................... {}", mark(reliable));
201+
println!(" golden phase separates episodes ..................... {}", mark(separates));
202+
let all = convergent && discriminant && phase_indep && reliable && separates;
203+
println!(
204+
"\n ⇒ instrument {}",
205+
if all {
206+
"VALIDATED — syntax=angle ⊥ semantics=residue ⊥ basin=phase is a real 3-axis basis"
207+
} else {
208+
"NEEDS CORRECTION — at least one axis conflates; see failed cells"
209+
}
210+
);
211+
}

0 commit comments

Comments
 (0)