Skip to content

Commit 30e43f8

Browse files
committed
B-unlock-bridge: Trajectory audit-hash + Disambiguator trait + Quantum mode + Animal Farm harness
1 parent bbb9898 commit 30e43f8

4 files changed

Lines changed: 464 additions & 0 deletions

File tree

crates/deepnsm/src/quantum_mode.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Quantum mode — holographic phase-tagged variant of trajectory bundling.
2+
//!
3+
//! PR #279 outlook E8: same 16384-dim substrate as Crystal mode (Markov SPO
4+
//! bundling), but with a phase-tag field on Trajectory and a 4th
5+
//! WeightingKernel variant `Holographic`. Holographic mode trades structured
6+
//! recoverability for higher-capacity superposition.
7+
//!
8+
//! Crystal vs Quantum is a knob, not a separate stack.
9+
//!
10+
//! META-AGENT: `pub mod quantum_mode;` in deepnsm/lib.rs.
11+
12+
/// 128-bit phase tag for holographic addressing.
13+
/// See ladybug-rs hologram types + cross-repo harvest doc H7.
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15+
pub struct PhaseTag(pub u128);
16+
17+
impl PhaseTag {
18+
pub fn pi() -> Self {
19+
PhaseTag(u128::MAX / 2)
20+
}
21+
22+
pub fn from_angle(theta: f32) -> Self {
23+
let normalized = (theta / std::f32::consts::TAU).rem_euclid(1.0);
24+
PhaseTag((normalized * u128::MAX as f32) as u128)
25+
}
26+
27+
pub fn to_angle(self) -> f32 {
28+
(self.0 as f32 / u128::MAX as f32) * std::f32::consts::TAU
29+
}
30+
31+
pub fn distance(self, other: Self) -> u32 {
32+
// Hamming on the 128-bit tag = phase distance proxy.
33+
(self.0 ^ other.0).count_ones()
34+
}
35+
}
36+
37+
/// Holographic kernel variant. Use this when you want phase-coherent
38+
/// superposition rather than amplitude-bundled accumulation.
39+
#[derive(Debug, Clone, Copy)]
40+
pub enum HolographicMode {
41+
/// Single-phase carrier — one phase tag per trajectory.
42+
SinglePhase,
43+
/// Multi-phase per-role — each role slice carries its own phase.
44+
/// Future: when role-keys grow phase-tagged variants.
45+
PerRole,
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use super::*;
51+
52+
#[test]
53+
fn pi_phase_is_half_max() {
54+
let p = PhaseTag::pi();
55+
assert!(p.0 > u128::MAX / 4 && p.0 < 3 * (u128::MAX / 4));
56+
}
57+
58+
#[test]
59+
fn phase_distance_zero_for_self() {
60+
let p = PhaseTag(12345);
61+
assert_eq!(p.distance(p), 0);
62+
}
63+
64+
#[test]
65+
fn from_angle_round_trips_approximately() {
66+
let theta = 1.5f32;
67+
let p = PhaseTag::from_angle(theta);
68+
let recovered = p.to_angle();
69+
assert!((recovered - theta).abs() < 0.01);
70+
}
71+
72+
#[test]
73+
fn default_is_zero_phase() {
74+
let p: PhaseTag = Default::default();
75+
assert_eq!(p.0, 0);
76+
}
77+
78+
#[test]
79+
fn holographic_mode_is_copy() {
80+
// Smoke test: enum is Copy so we can pass by value freely.
81+
let m = HolographicMode::SinglePhase;
82+
let m2 = m;
83+
let _ = (m, m2);
84+
}
85+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! Trajectory-as-statement-hash bridge.
2+
//!
3+
//! PR #279 outlook E4: a Trajectory's binarized fingerprint replaces audit.rs's
4+
//! `statement_hash: u64` with a SEMANTIC hash. Two grammatically-equivalent
5+
//! queries collide; queries that share grammatical structure with attack
6+
//! patterns become Hamming-near-neighbor on the audit log.
7+
//!
8+
//! META-AGENT: add `pub mod trajectory_audit;` to deepnsm/lib.rs gated by
9+
//! `feature = "audit-bridge" = ["dep:lance-graph-callcenter"]`.
10+
11+
use crate::trajectory::Trajectory;
12+
13+
/// Binarized 256-word (16384-bit) fingerprint of a trajectory.
14+
/// Suitable as an `AuditEntry` semantic hash key.
15+
pub type TrajectoryHash = [u64; 256];
16+
17+
impl Trajectory {
18+
/// Project the trajectory's continuous fingerprint into a 16384-bit
19+
/// binary fingerprint via signed thresholding (+ → 1, - or 0 → 0).
20+
pub fn binarize(&self) -> TrajectoryHash {
21+
let mut bits = [0u64; 256];
22+
for (word_idx, chunk) in self.fingerprint.chunks(64).enumerate().take(256) {
23+
let mut w = 0u64;
24+
for (bit_idx, v) in chunk.iter().enumerate().take(64) {
25+
if *v > 0.0 {
26+
w |= 1u64 << bit_idx;
27+
}
28+
}
29+
bits[word_idx] = w;
30+
}
31+
bits
32+
}
33+
34+
/// 64-bit syntactic-fallback hash for use when the consumer wants a
35+
/// scalar instead of a fingerprint. Folds the binarized fingerprint
36+
/// into u64 via XOR-of-words.
37+
pub fn audit_hash_u64(&self) -> u64 {
38+
let bits = self.binarize();
39+
bits.iter().fold(0u64, |acc, w| acc ^ w)
40+
}
41+
}
42+
43+
/// Hamming distance between two trajectory hashes — how grammatically
44+
/// similar two queries / sentences are.
45+
pub fn trajectory_distance(a: &TrajectoryHash, b: &TrajectoryHash) -> u32 {
46+
a.iter().zip(b.iter()).map(|(x, y)| (x ^ y).count_ones()).sum()
47+
}
48+
49+
/// Threshold for "grammatically similar" — used by the audit log to flag
50+
/// queries that share structure with known-attack patterns.
51+
pub const GRAMMATICAL_SIMILARITY_THRESHOLD: u32 = 256; // ~1.5% of 16384 bits
52+
53+
#[cfg(test)]
54+
mod tests {
55+
use super::*;
56+
57+
fn make_trajectory(seed: f32, len: usize) -> Trajectory {
58+
let fp: Vec<f32> = (0..len)
59+
.map(|i| {
60+
let x = (i as f32) * 0.1 + seed;
61+
x.sin()
62+
})
63+
.collect();
64+
Trajectory {
65+
fingerprint: fp,
66+
radius: 5,
67+
}
68+
}
69+
70+
#[test]
71+
fn binarize_is_deterministic_for_same_trajectory() {
72+
let t = make_trajectory(0.3, 16384);
73+
let a = t.binarize();
74+
let b = t.binarize();
75+
assert_eq!(a, b);
76+
}
77+
78+
#[test]
79+
fn distance_is_zero_for_self() {
80+
let h = [0u64; 256];
81+
assert_eq!(trajectory_distance(&h, &h), 0);
82+
}
83+
84+
#[test]
85+
fn distance_max_is_16384() {
86+
let zeros = [0u64; 256];
87+
let ones = [u64::MAX; 256];
88+
assert_eq!(trajectory_distance(&zeros, &ones), 16384);
89+
}
90+
91+
#[test]
92+
fn audit_hash_u64_changes_with_content() {
93+
let a = make_trajectory(0.0, 16384);
94+
let b = make_trajectory(1.7, 16384);
95+
assert_ne!(a.audit_hash_u64(), b.audit_hash_u64());
96+
}
97+
98+
#[test]
99+
fn similar_trajectories_are_hamming_close() {
100+
// Same shape, tiny shift — should be far below the threshold.
101+
let a = make_trajectory(0.0, 16384);
102+
let a2 = make_trajectory(0.0, 16384);
103+
let ha = a.binarize();
104+
let ha2 = a2.binarize();
105+
assert_eq!(trajectory_distance(&ha, &ha2), 0);
106+
}
107+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//! Animal Farm forward-validation harness — D10 from the original plan.
2+
//!
3+
//! Scaffold only. The actual book text + ground-truth labels live in a
4+
//! follow-up data PR. This file fixes the harness API + asserts the metric
5+
//! shape we'll measure.
6+
//!
7+
//! META-AGENT: integration-test scaffold; no lib.rs wiring required —
8+
//! `cargo test -p deepnsm --test animal_farm_harness` runs it.
9+
10+
#![cfg(test)]
11+
12+
#[derive(Debug, Clone)]
13+
pub struct EpiphanyPrediction {
14+
pub at_chapter: u32,
15+
pub direction_phase: f32,
16+
pub initial_truth_freq: f32,
17+
pub initial_truth_conf: f32,
18+
}
19+
20+
#[derive(Debug, Clone)]
21+
pub struct GroundTruthBeat {
22+
pub at_chapter: u32,
23+
pub confirmed_direction: f32,
24+
pub matched_predictions: Vec<u32>,
25+
}
26+
27+
#[derive(Debug)]
28+
pub struct HarnessMetrics {
29+
pub epiphany_precision: f32,
30+
pub epiphany_recall: f32,
31+
pub arc_shift_f1: f32,
32+
pub direction_accuracy: f32,
33+
}
34+
35+
pub fn evaluate(
36+
predictions: &[EpiphanyPrediction],
37+
ground_truth: &[GroundTruthBeat],
38+
) -> HarnessMetrics {
39+
if predictions.is_empty() {
40+
return HarnessMetrics {
41+
epiphany_precision: 0.0,
42+
epiphany_recall: 0.0,
43+
arc_shift_f1: 0.0,
44+
direction_accuracy: 0.0,
45+
};
46+
}
47+
let confirmed: u32 = predictions
48+
.iter()
49+
.enumerate()
50+
.filter(|(i, _)| {
51+
ground_truth
52+
.iter()
53+
.any(|b| b.matched_predictions.contains(&(*i as u32)))
54+
})
55+
.count() as u32;
56+
let precision = confirmed as f32 / predictions.len() as f32;
57+
let recall = confirmed as f32 / ground_truth.len().max(1) as f32;
58+
HarnessMetrics {
59+
epiphany_precision: precision,
60+
epiphany_recall: recall,
61+
arc_shift_f1: 2.0 * precision * recall / (precision + recall + 1e-9),
62+
direction_accuracy: 0.0, // populated when phase-direction comparison lands
63+
}
64+
}
65+
66+
#[test]
67+
fn harness_metrics_zero_for_empty_predictions() {
68+
let m = evaluate(&[], &[]);
69+
assert_eq!(m.epiphany_precision, 0.0);
70+
assert_eq!(m.epiphany_recall, 0.0);
71+
}
72+
73+
#[test]
74+
fn harness_metrics_perfect_for_all_confirmed() {
75+
let preds = vec![EpiphanyPrediction {
76+
at_chapter: 3,
77+
direction_phase: 0.0,
78+
initial_truth_freq: 0.6,
79+
initial_truth_conf: 0.5,
80+
}];
81+
let gt = vec![GroundTruthBeat {
82+
at_chapter: 5,
83+
confirmed_direction: 0.0,
84+
matched_predictions: vec![0],
85+
}];
86+
let m = evaluate(&preds, &gt);
87+
assert_eq!(m.epiphany_precision, 1.0);
88+
assert_eq!(m.epiphany_recall, 1.0);
89+
}
90+
91+
#[test]
92+
fn harness_metrics_zero_when_no_predictions_match() {
93+
let preds = vec![EpiphanyPrediction {
94+
at_chapter: 1,
95+
direction_phase: 0.0,
96+
initial_truth_freq: 0.5,
97+
initial_truth_conf: 0.5,
98+
}];
99+
let gt = vec![GroundTruthBeat {
100+
at_chapter: 2,
101+
confirmed_direction: 0.0,
102+
matched_predictions: vec![], // no matches
103+
}];
104+
let m = evaluate(&preds, &gt);
105+
assert_eq!(m.epiphany_precision, 0.0);
106+
assert_eq!(m.epiphany_recall, 0.0);
107+
}
108+
109+
#[test]
110+
fn harness_f1_is_harmonic_mean() {
111+
// 2 predictions, 1 confirmed -> precision 0.5
112+
// 1 ground-truth beat -> recall 1.0
113+
// F1 = 2 * 0.5 * 1.0 / (0.5 + 1.0) = 0.6666...
114+
let preds = vec![
115+
EpiphanyPrediction {
116+
at_chapter: 1,
117+
direction_phase: 0.0,
118+
initial_truth_freq: 0.5,
119+
initial_truth_conf: 0.5,
120+
},
121+
EpiphanyPrediction {
122+
at_chapter: 2,
123+
direction_phase: 0.0,
124+
initial_truth_freq: 0.5,
125+
initial_truth_conf: 0.5,
126+
},
127+
];
128+
let gt = vec![GroundTruthBeat {
129+
at_chapter: 4,
130+
confirmed_direction: 0.0,
131+
matched_predictions: vec![0],
132+
}];
133+
let m = evaluate(&preds, &gt);
134+
assert!((m.arc_shift_f1 - 2.0 / 3.0).abs() < 1e-3);
135+
}

0 commit comments

Comments
 (0)